Threading в приложении PyQt: используйте потоки Qt или потоки Python?

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

[Да, я знаю, теперь у меня две проблемы.]

В любом случае, приложение использует PyQt4, поэтому я хотел бы знать, какой лучший выбор: использовать потоки Qt или использовать модуль Python threading? Каковы преимущества/недостатки каждого? Или у вас есть совершенно другое предложение?

Изменить (re bounty):. Хотя решение в моем конкретном случае, вероятно, будет использовать неблокирующий сетевой запрос, например Jeff Ober и Lukáš Lalinský (так что в основном оставляя проблемы concurrency для сетевой реализации), мне все же хотелось бы получить более подробный ответ на общий вопрос:

Каковы преимущества и недостатки использования потоков PyQt4 (т.е. Qt) поверх собственных потоков Python (из модуля threading)?


Изменить 2: Спасибо всем за ответы. Несмотря на то, что нет 100% -ного соглашения, похоже, широко распространено мнение, что ответ "используется Qt", поскольку преимущество этого заключается в интеграции с остальной библиотекой, не создавая реальных недостатков.

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

Было несколько ответов, которые я рассмотрел за щедрость; в конце концов я выбрал аббата для очень релевантной внешней ссылки; это был, однако, близкий звонок.

Еще раз спасибо.

Ответ 1

Это было обсуждено не так давно в списке рассылки PyQt. Цитируя Giovanni Bajo комментарии по теме:

Это в основном то же самое. Основное различие заключается в том, что QThreads лучше интегрированный с Qt (асинхронные сигналы/слоты, цикл событий и т.д.). Кроме того, вы не можете использовать Qt из потока Python (например, вы не можете отправить сообщение в основной поток через QApplication.postEvent): вы нужен QThread для работы.

Общим правилом может быть использование QThreads, если вы собираетесь каким-то образом взаимодействовать с Qt и использовать потоки Python в противном случае.

И некоторые более ранние комментарии по этому вопросу от автора PyQt: "они оба являются обертками вокруг тех же встроенных реализаций потока". И обе реализации используют GIL таким же образом.

Ответ 2

Нити Python будут проще и безопаснее, и поскольку они предназначены для приложения на основе ввода-вывода, они могут обойти GIL. Тем не менее, рассматривали ли вы неблокирующий ввод-вывод с использованием витых или неблокирующих сокетов /select?

EDIT: больше на потоках

Нити Python

Нити Python - это системные потоки. Тем не менее, Python использует глобальную блокировку интерпретатора (GIL), чтобы гарантировать, что интерпретатор выполняет только определенный блок размера инструкций байтового кода за раз. К счастью, Python выпускает GIL во время операций ввода-вывода, делая потоки полезными для имитации неблокирующего ввода-вывода.

Важное предупреждение: Это может ввести в заблуждение, так как количество инструкций байт-кода не соответствует количеству строк в программе. Даже одно назначение не может быть атомарным в Python, поэтому блокировка мьютекса необходима для любого блока кода, который должен выполняться атомарно, даже с GIL.

Темы QT

Когда Python отключает управление сторонним компилируемым модулем, он освобождает GIL. В обязанности модуля входит обеспечение атомарности, когда это необходимо. Когда управление передается обратно, Python будет использовать GIL. Это может сделать использование сторонних библиотек в связи с запутанностью потоков. Еще сложнее использовать внешнюю библиотеку потоков, поскольку она добавляет неопределенность относительно того, где и когда управление находится в руках модуля и интерпретатора.

Потоки QT работают с выпущенным GIL. Потоки QT могут выполнять код библиотеки QT (и другой код скомпилированного модуля, который не получает GIL) одновременно. Однако код Python, выполняемый в контексте потока QT, по-прежнему приобретает GIL, и теперь вам нужно управлять двумя наборами логики для блокировки вашего кода.

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

Неблокирующий ввод/вывод

Темы добавляют необычайную сложность вашему приложению. Особенно при работе с уже сложным взаимодействием между интерпретатором Python и скомпилированным модульным кодом. В то время как многие находят, что программирование на основе событий сложно отслеживать, на основе событий, неблокирующий ввод-вывод часто гораздо труднее рассуждать о потоках.

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

Одним из приятных решений для неблокирующего ввода-вывода на основе событий является новая библиотека Diesel. На данный момент он ограничен Linux, но он необычайно быстрый и довольно элегантный.

Также стоит потратить время на изучение pyevent, обертки вокруг замечательной библиотеки libevent, которая предоставляет базовую структуру для event- основанного на программировании, используя самый быстрый доступный для вашей системы метод (определенный во время компиляции).

Ответ 3

Преимущество QThread заключается в том, что он интегрирован с остальной библиотекой Qt. То есть, методы, ориентированные на поток в Qt, должны знать, в каком потоке они выполняются, и перемещать объекты между потоками, вам нужно будет использовать QThread. Еще одна полезная функция - запустить собственный цикл событий в потоке.

Если вы обращаетесь к HTTP-серверу, вы должны рассмотреть QNetworkAccessManager.

Ответ 4

Я задал себе тот же вопрос, когда я работал над PyTalk.

Если вы используете Qt, вам нужно использовать QThread, чтобы иметь возможность использовать структуру Qt и, в первую очередь, систему сигналов/слотов.

С движком сигнала/слота вы сможете разговаривать из потока в другой и с каждой частью вашего проекта.

Более того, вопрос об этом выборе очень невелик, так как оба являются связями С++.

Вот мой опыт PyQt и потока.

Я рекомендую вам использовать QThread.

Ответ 5

У Джеффа есть хорошие моменты. Только один основной поток может выполнять любые обновления графического интерфейса пользователя. Если вам нужно обновить графический интерфейс из потока, Qt-4 оповещение о подключении позволяет легко отправлять данные по потокам и автоматически вызывается, если вы используете QThread; Я не уверен, что они будут, если вы используете потоки Python, хотя легко добавить параметр в connect().

Ответ 6

Я тоже не могу рекомендовать, но я могу попробовать описать различия между потоками CPython и Qt.

Прежде всего, потоки CPython не запускаются одновременно, по крайней мере, не с кодом Python. Да, они создают системные потоки для каждого потока Python, однако разрешен запуск только потока, в котором сейчас выполняется Global Interpreter Lock (расширения C и код FFI могут обходить его, но байт-код Python не выполняется, пока поток не содержит GIL).

С другой стороны, у нас есть потоки Qt, которые в основном являются общим уровнем по системным потокам, не имеют Global Interpreter Lock и, следовательно, способны работать одновременно. Я не уверен, как PyQt справляется с этим, однако, если ваши потоки Qt не назовут код Python, они должны иметь возможность запускать одновременно (блокировать различные дополнительные блокировки, которые могут быть реализованы в разных структурах).

Для дополнительной тонкой настройки вы можете изменить количество инструкций байт-кода, которые интерпретируются до переключения права собственности на GIL - более низкие значения означают большее переключение контекста (и, возможно, более высокую отзывчивость), но более низкую производительность для каждого потока (коммутаторы контекста имеют свою стоимость - если вы попытаетесь переключить каждую инструкцию, это не ускорит скорость.)

Надеюсь, что это поможет с вашими проблемами:)

Ответ 7

Я не могу прокомментировать точные различия между потоками Python и PyQt, но я делал то, что вы пытаетесь сделать, используя QThread, QNetworkAcessManager и убедившись, что вы вызываете QApplication.processEvents(), в то время как поток жив. Если отзывчивость GUI действительно является проблемой, которую вы пытаетесь решить, более поздняя версия поможет.