Twisted: использование нескольких потоков и процессов вместе - PullRequest
4 голосов
/ 19 мая 2011

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

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

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

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

Ответы [ 4 ]

4 голосов
/ 19 мая 2011

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

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

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

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

2 голосов
/ 24 августа 2011

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

reactor.callFromThread(reactor.spawnProcess, *spawnargs)

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

reactor.spawnProcess(*spawnargs)

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

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

2 голосов
/ 19 мая 2011

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

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

Во-вторых, fork() не создает несколько потоков в дочернем процессе. В соответствии со стандартом, описывающим fork(),

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

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

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

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

Так что, пожалуйста, будьте более конкретны в отношении вашей конкретной проблемы, посколькуподпроцесс с клонируемыми потоками.

1 голос
/ 27 июня 2011

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

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

...