Витой:совместное использование нескольких потоков и процессов

StackOverflow https://stackoverflow.com/questions/6052043

Вопрос

Документация Twisted заставила меня поверить, что можно комбинировать такие методы, как reactor.spawnProcess() и threads.deferToThread() в том же приложении, что реактор элегантно справится с этим под одеялом.Попробовав это, я обнаружил, что мое приложение блокируется.При использовании нескольких потоков или дочерних процессов все в порядке.

Заглянув в источник реактора, я обнаружил, что SelectReactor.spawnProcess() метод просто вызывает os.fork() без какого-либо учета нескольких потоков, которые могут выполняться.Это объясняет взаимоблокировки, поскольку начиная с вызова os.fork() у вас будет два процесса с несколькими параллельными потоками, которые выполняют бог знает что с одними и теми же файловыми дескрипторами.

Мой вопрос к SO: какова наилучшая стратегия решения этой проблемы?

Я имею в виду подкласс SelectReactor, так что это синглтон и вызывает os.fork() только один раз, сразу после создания экземпляра.Дочерний процесс будет работать в фоновом режиме и выступать в качестве сервера для родительского процесса (используя сериализацию объектов по каналам для обмена данными туда и обратно).Родитель продолжает запускать приложение и может использовать потоки по своему усмотрению.Звонки в spawnProcess() в родителе будет делегирован дочернему процессу, который гарантированно будет запускать только один поток и, следовательно, может вызывать os.fork() безопасно.

Кто-нибудь делал это раньше?Есть ли более быстрый способ?

Это было полезно?

Решение 3

Возвращаясь к этому вопросу через некоторое время, я обнаружил, что если я сделаю это:

reactor.callFromThread(reactor.spawnProcess, *spawnargs)

вместо этого:

reactor.spawnProcess(*spawnargs)

тогда проблема исчезнет в моем небольшом тестовом примере.В документации Twisted «Использование процессов» есть замечание, которое побудило меня попробовать это:«Большая часть кода в Twisted не является потокобезопасной.Например, запись данных в транспорт из протокола не является потокобезопасной».

Я подозреваю, что другие люди, о которых упомянул Жан-Поль, столкнулись с этой проблемой, возможно, совершают аналогичную ошибку.Приложение несет ответственность за обеспечение того, чтобы реактор и другие вызовы API выполнялись в правильном потоке.И, судя по всему, за очень узкими исключениями, «правильный поток» почти всегда является основным потоком реактора.

Другие советы

Какова наилучшая стратегия решения этой проблемы?

Подать заявку (возможно, после регистрация), описывающее проблему, желательно с помощью воспроизводимого тестового примера (для максимальной точности).Затем может быть проведена дискуссия о том, каким может быть лучший способ (или способы — разные платформы могут требовать разных решений) для его реализации.

Идея немедленного создания дочернего процесса, чтобы помочь в дальнейшем создании дочернего процесса, уже поднималась, чтобы решить проблему производительности, связанную с сбором данных дочернего процесса.Если этот подход теперь решит две проблемы, он начнет выглядеть немного более привлекательным.Одна из потенциальных трудностей этого подхода заключается в том, что spawnProcess синхронно возвращает объект, который предоставляет PID дочернего элемента и позволяет отправлять ему сигналы.Это требует немного больше работы, если на пути есть промежуточный процесс, поскольку перед этим PID необходимо будет передать обратно основному процессу. spawnProcess возвращается.Аналогичная задача будет заключаться в поддержке childFDs аргумент, поскольку больше невозможно будет просто наследовать файловые дескрипторы в дочернем процессе.

Альтернативное решение (которое может быть несколько более хакерским, но также может иметь меньше проблем с реализацией) может заключаться в вызове sys.setcheckinterval с очень большим номером перед звонком os.fork, а затем восстановите исходный интервал проверки только в родительском процессе.Этого должно быть достаточно, чтобы избежать переключения потоков в процессе до тех пор, пока не os.execvpe происходит, уничтожая все лишние потоки.Это не совсем правильно, поскольку некоторые ресурсы (например, мьютексы и условия) останутся в плохом состоянии, но вы используете их с deferToThread не очень распространено, так что, возможно, это не влияет на ваш случай.

Совет, который Жан-Поль дает в своем ответе, хорош, но это должен работает (и работает в большинстве случаев).

Во-первых, Twisted также использует потоки для разрешения имен хостов, и я определенно использовал в процессах Twisted подпроцессы, которые также устанавливают клиентские соединения.Так что это может сработать на практике.

Второй, fork() не создает несколько потоков в дочернем процессе. Согласно стандартному описанию fork(),

Процесс должен быть создан с одним потоком.Если многопоточный процесс вызывает fork(), новый процесс должен содержать копию вызывающего потока...

Это не значит, что есть нет потенциальные проблемы с многопоточностью spawnProcess;стандарт также говорит:

...чтобы избежать ошибок, дочерний процесс может выполнять операции, безопасные для асинхронных сигналов, только до тех пор, пока не будет вызвана одна из функций exec...

и я не думаю, что есть что-то, что могло бы гарантировать использование только операций, безопасных для асинхронных сигналов.

Поэтому, пожалуйста, уточните конкретную проблему, поскольку это не подпроцесс с клонированием потоков.

fork() в Linux определенно оставляет дочерний процесс только с одним потоком.

Я предполагаю, что вы знаете, что при использовании потоков в Twisted ЕДИНСТВЕННЫЙ Twisted API, который потокам разрешено вызывать, — это callFromThread?Все остальные API-интерфейсы Twisted должны вызываться только из основного потока реактора.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top