Нитка в додатку PyQt: Використовуйте потоки Qt або потоки Python?


116

Я пишу програму GUI, яка регулярно завантажує дані через веб-з'єднання. Оскільки цей пошук займає деякий час, це призводить до того, що інтерфейс користувача не реагує на процес пошуку (його неможливо розділити на менші частини). Ось чому я хотів би передати веб-з'єднання окремим робочим потоком.

[Так, я знаю, зараз у мене дві проблеми .]

У будь-якому разі, програма використовує PyQt4, тож я хотів би знати, який кращий вибір: використовувати нитки Qt або використовувати threadingмодуль Python ? Які переваги / недоліки кожного? Або у вас є зовсім інша пропозиція?

Редагувати (re bounty): Хоча рішення в моєму конкретному випадку, ймовірно, буде використовувати мережевий запит, що не блокує, як, наприклад, запропонували Джефф Обер та Лукаш Лалінські (так що, як правило, проблеми з паралельною кон'юнктурою залишаються в реалізації мережі), я все одно хотів би більше поглиблена відповідь на загальне питання:

Які переваги та недоліки використання потоків PyQt4 (тобто Qt) над рідними потоками Python (з threadingмодуля)?


Редагувати 2: Дякую за всі відповіді. Хоча немає 100% згоди, мабуть, існує широкий консенсус, що відповідь - «використовувати Qt», оскільки перевагою цього є інтеграція з рештою бібліотеки, не створюючи при цьому реальних недоліків.

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

Було кілька відповідей, які я вважав за щедроту; врешті-решт я вибрав ігумена для дуже актуального зовнішнього посилання; це був, однак, тісний дзвінок.

Знову дякую.

Відповіді:


106

Про це не так давно йшлося в списку розсилки PyQt. Цитуючи коментарі Джованні Баджо з цього приводу:

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

Загальним правилом може бути використання QThreads, якщо ви хочете якось взаємодіяти з Qt, а іншим чином використовувати потоки Python.

І дещо раніше прокоментував цю проблему від автора PyQt: "вони обоє обгортки навколо одних і тих же реалізацій натільних потоків". І обидві реалізації використовують GIL однаково.


2
Хороша відповідь, але я думаю, що вам слід скористатися кнопкою блокчету, щоб чітко показати, де ви насправді не підсумовуєте, а цитуєте Джованні Баджо зі списку розсилки :)
c089

2
Цікаво, чому ви не можете розміщувати події в основному потоці через QApplication.postEvent () і вам потрібен QThread для цього? Я думаю, я бачив людей, які роблять це, і це спрацювало.
Триларіон

1
Я зателефонував QCoreApplication.postEventіз потоку Python зі швидкістю 100 разів на секунду в додатку, який працює на крос-платформі і перевірявся протягом 1000 годин. Я ніколи з цього не бачив жодних проблем. Я думаю, що це добре, поки об'єкт призначення знаходиться в MainThread або QThread. Я також загорнув його у приємну бібліотеку, дивіться qtutils .
three_pineapples

2
Враховуючи сильно підкреслений характер цього питання та відповіді, я думаю, що варто звернути увагу на недавню відповідь ЕК, що розглядає умови, в яких безпечно використовувати певні методи Qt з потоків Python. Це співпадає із спостережуваною поведінкою, яку бачив я і @Trilarion.
three_pineapples

33

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

EDIT: докладніше про теми

Нитки Python

Нитки Python - це системні потоки. Однак Python використовує глобальне блокування інтерпретатора (GIL), щоб гарантувати, що інтерпретатор виконує лише певний блок розмірів інструкцій байт-коду одночасно. На щастя, Python вивільняє GIL під час операцій введення / виводу, роблячи потоки корисними для імітації неблокуючих вводу / виводу.

Важливий застереження: Це може ввести в оману, оскільки кількість інструкцій байт-коду не відповідає кількості рядків у програмі. Навіть одне призначення не може бути атомним у Python, тому блокування mutex необхідне для будь-якого блоку коду, який повинен виконуватися атомно, навіть з 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, яка забезпечує основну основу для програмування на основі подій, використовуючи найшвидший доступний метод для вашої системи (визначається під час компіляції).


Re Twisted etc.: я використовую сторонні бібліотеки, які виконують фактичні роботи в мережі; Я хотів би уникнути латання в ньому. Але я все одно буду розбиратися в цьому, дякую.
balpha

2
Ніщо насправді не обходить GIL. Але Python випускає GIL під час операцій вводу / виводу. Python також випускає GIL, коли він «передає» компільовані модулі, які відповідають за придбання / випуск самих GIL.
Джефф Обер

2
Оновлення просто неправильне. Код Python працює точно так само, як у потоці Python, ніж у QThread. Ви отримуєте GIL, коли запускаєте код Python (а потім Python керує виконанням між потоками), ви звільняєте його, коли запускаєте код C ++. Різниці взагалі немає.
Лукаш Лалінськ

1
Ні, справа в тому, що як би ви не створювали нитку, інтерпретатор Python не хвилює. Все, що її хвилює, - це те, що він може придбати GIL і після X інструкцій може випустити / знову придбати його. Наприклад, ви можете використовувати ctypes для створення зворотного дзвінка з бібліотеки C, який буде викликаний окремим потоком, і код буде добре працювати, навіть не знаючи, що це інший потік. Насправді нічого особливого в модулі потоку немає.
Лукаш Лалінськ

1
Ви говорили, чим QThread відрізняється щодо блокування та як "вам потрібно керувати двома наборами логіки для блокування коду". Що я кажу, що це зовсім не відрізняється. Я можу використовувати ctypes та pthread_create для запуску потоку, і він буде працювати точно так само. Python-код просто не повинен дбати про GIL.
Лукаш Лалінськ

21

Перевага QThreadполягає в тому, що вона інтегрована з рештою бібліотеки Qt. Тобто для методів, що знають потоки в Qt, потрібно знати, в якому потоці вони запущені, і для переміщення об'єктів між потоками вам потрібно буде скористатися QThread. Ще одна корисна функція - це запуск у потоці власного циклу подій.

Якщо ви отримуєте доступ до HTTP-сервера, вам слід врахувати QNetworkAccessManager.


1
Крім того, що я коментував відповідь Джеффа Оббера, QNetworkAccessManagerвиглядає багатообіцяючим. Дякую.
balpha

13

Я задавав собі те саме питання, коли працював у PyTalk .

Якщо ви використовуєте Qt, вам потрібно використовувати, QThreadщоб мати можливість використовувати рамку Qt, а особливо систему сигналів / слотів.

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

Більше того, питання щодо цього вибору не дуже важливе, оскільки обидва є прив'язкою C ++.

Ось мій досвід PyQt та потоку.

Я заохочую вас використовувати QThread.


9

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


5

Я теж не можу рекомендувати, але можу спробувати описати відмінності між потоками CPython та Qt.

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

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

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

Сподіваюся, що це допоможе у ваших проблемах :)


7
Тут важливо зауважити: PyQt QThreads дійсно приймають блокування глобального інтерпретатора . Весь код Python блокує GIL, і будь-який QThreads, який ви запускаєте в PyQt, буде працювати з кодом Python. (Якщо їх немає, ви фактично не використовуєте "Py" частину PyQt :). Якщо ви вирішите відкласти цей код Python у зовнішню бібліотеку С, GIL буде випущений, але це правда незалежно від того, використовуєте ви нитку Python чи потік Qt.
кварк

Це було власне те, що я намагався довести, що весь код Python бере блокування, але це не має значення для коду C / C ++, який працює в окремій потоці
p_l

0

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


1
QNetworkAcessManagerне вимагає нитки або processEvents. Він використовує асинхронні операції вводу-виводу.
Лукаш Лалінськийý

Ой, так, я використовую комбінацію QNetworkAcessManagerта httplib2. Використовується мій код асинхронізації httplib2.
brianz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.