PyQt应用程序中的线程:使用Qt线程还是Python线程?


116

我正在编写一个GUI应用程序,该应用程序通过Web连接定期检索数据。由于此检索需要一段时间,因此会导致UI在检索过程中无响应(无法拆分成较小的部分)。这就是为什么我想将Web连接外包给单独的工作线程。

[是的,我知道,现在我有两个问题。]

无论如何,该应用程序使用PyQt4,所以我想知道更好的选择是:使用Qt的线程还是使用Python threading模块?各自的优点/缺点是什么?还是您有完全不同的建议?

编辑(赏金):虽然在我的特定情况下,解决方案可能会使用非阻塞网络请求,例如Jeff OberLukášLalinský建议的(所以基本上将并发性问题留给了网络实现),但我仍然想要更多深入回答一般问题:

与本地Python线程(来自threading模块)相比,使用PyQt4(即Qt)线程有什么优缺点?


编辑2:谢谢大家的回答。尽管没有达成100%的协议,但似乎普遍的共识是答案是“使用Qt”,因为这样做的优点是可以与库的其余部分集成,而不会造成任何实际的缺点。

对于希望在这两种线程实现之间进行选择的任何人,我强烈建议他们阅读此处提供的所有答案,包括方丈链接到的PyQt邮件列表线程。

我考虑了一些悬赏的答案;最后,我选择了方丈作为非常相关的外部参考;然而,这是一个密切的电话。

再次感谢。

Answers:


106

不久前在PyQt邮件列表中对此进行了讨论。引用乔凡尼·巴霍(Giovanni Bajo)对这个问题的评论

大致相同。主要区别在于QThreads与Qt(异步信号/插槽,事件循环等)更好地集成在一起。另外,您不能在Python线程中使用Qt(例如,不能通过QApplication.postEvent将事件发布到主线程):您需要一个QThread才能工作。

一般的经验法则是,如果您要以某种方式与Qt进行交互,则可以使用QThreads;否则,请使用Python线程。

PyQt的作者对此主题有较早的评论:“它们都是相同的本机线程实现的包装器”。两种实现都以相同的方式使用GIL。


2
好的答案,但是我认为您应该使用blockquote按钮清楚地表明您实际上不是在汇总,而是从邮件列表中引用Giovanni Bajo :)
c089 2010年

2
我想知道为什么您不能通过QApplication.postEvent()将事件发布到主线程,并为此需要QThread?我想我已经看到有人这样做了,而且奏效了。
Trilarion

1
我已经QCoreApplication.postEvent在跨平台运行的应用程序中以每秒100次的速度从Python线程中进行调用,并且已经测试了1000个小时。我从没有见过任何问题。我认为只要目标对象位于MainThread或QThread中就可以了。我还将其包装在一个不错的库中,请参阅qtutils
three_pineapples '18年

2
考虑到该问题和答案的高度争议性,我认为值得指出的是ekhumoro最近提出的SO答案,其中详细说明了可以安全地使用Python线程中某些Qt方法的条件。这符合我和@Trilarion所观察到的行为。
three_pineapples '18

33

Python的线程将更简单,更安全,并且由于它用于基于I / O的应用程序,因此它们能够绕过GIL。也就是说,您是否考虑过使用Twisted或非阻塞套接字/选择的非阻塞I / O?

编辑:更多关于线程

Python线程

Python的线程是系统线程。但是,Python使用全局解释器锁(GIL)来确保解释器一次只执行一定大小的字节码指令块。幸运的是,Python在输入/输出操作期间释放了GIL,使线程可用于模拟非阻塞I / O。

重要警告:这可能会引起误解,因为字节码指令的数量与程序中的行数对应。在Python中,即使是单个分配也可能不是原子分配的,因此对于必须原子执行的任何代码块,即使使用GIL,也需要互斥锁。

QT线程

当Python将控制权交给第三方编译模块时,它将释放GIL。在需要时,确保原子性成为模块的责任。当控制权回传时,Python将使用GIL。这可能会使第3方库与线程混淆一起使用。使用外部线程库更加困难,因为它增加了控制权在何时何地掌握在模块和解释器之间的不确定性。

QT线程在释放GIL的情况下运行。QT线程能够同时执行QT库代码(以及其他不获取GIL的已编译模块代码)。然而,QT线程的上下文中执行的Python代码仍然取得GIL,现在你必须要管理2台逻辑的锁定你的代码。

最后,QT线程和Python线程都是系统线程的包装器。Python线程使用起来稍微安全些,因为那些不是用Python编写的部分(隐式使用GIL)在任何情况下都使用GIL(尽管上面的警告仍然适用)。

非阻塞I / O

线程给您的应用程序增加了极大的复杂性。特别是在处理Python解释器和已编译模块代码之间已经很复杂的交互时。尽管许多人发现很难遵循基于事件的编程,但是基于事件的非阻塞I / O通常比线程难得多。

使用异步I / O,您始终可以确保对于每个打开的描述符,执行路径是一致且有序的。显然,有一些必须解决的问题,例如当代码取决于一个打开的通道时该怎么办进一步取决于当另一个打开的通道返回数据时要调用的代码结果。

新的Diesel库是基于事件的非阻塞I / O的一种不错的解决方案。目前,它仅限于Linux,但是它非常快且非常优雅。

还值得您花时间学习pyevent,它是一个出色的libevent库的包装器,它为系统使用最快的可用方法(在编译时确定)提供了基于事件的编程的基本框架。


Re Twisted等:我使用第三方库来做实际的网络工作;我想避免在其中打补丁。但是,我还是会研究一下,谢谢。
balpha

2
实际上,没有什么绕过GIL。但是Python在I / O操作期间释放了GIL。当“移交给”已编译的模块时,Python也会释放GIL,这些模块负责获取/释放GIL本身。
杰夫·奥伯

2
该更新是错误的。Python代码在Python线程中的运行方式与在QThread中完全相同。当您运行Python代码时,您将释放GIL(然后由Python管理线程之间的执行),当您运行C ++代码时,您将其释放。完全没有区别。
卢卡什·拉林斯基09年

1
不,关键是无论您如何创建线程,Python解释器都不会在意。它关心的只是它可以获取GIL,并且在X指令后可以释放/重新获取它。例如,您可以使用ctypes从C库创建一个回调,该回调将在单独的线程中调用,并且代码可以正常工作,甚至不知道它是另一个线程。线程模块确实没有什么特别的。
卢卡什·拉林斯基2009年

1
您是在说QThread在锁定方面有何不同,以及“必须管理两组逻辑来锁定代码”。我的意思是根本没有什么不同。我可以使用ctypes和pthread_create启动线程,它的工作方式完全相同。Python代码根本不必关心GIL。
卢卡什·拉林斯基2009年

21

优点QThread是它与Qt库的其余部分集成在一起。也就是说,Qt中的线程感知方法将需要知道它们在哪个线程中运行,并且需要在线程之间移动对象QThread。另一个有用的功能是在线程中运行您自己的事件循环。

如果要访问HTTP服务器,则应考虑QNetworkAccessManager


1
除了我对杰夫·奥伯的回答发表的评论外,QNetworkAccessManager看起来很有希望。谢谢。
balpha

13

PyTalk上工作时,我问了同样的问题。

如果您使用的是Qt,则需要使用QThread能够使用Qt框架,尤其是信号/插槽系统。

使用信号/插槽引擎,您将能够从一个线程与另一个线程以及项目的每个部分进行对话。

而且,由于这两者都是C ++绑定,因此对于此选择没有太大的性能问题。

这是我对PyQt和线程的经验。

我鼓励你使用QThread


9

杰夫有一些优点。只有一个主线程可以执行任何GUI更新。如果确实需要从线程内更新GUI,则Qt-4的排队连接信号使跨线程发送数据变得容易,并且如果使用QThread,则将自动调用该信号。我不确定您是否正在使用Python线程,尽管可以轻松地向中添加参数connect()


5

我也不能真正推荐,但是我可以尝试描述CPython和Qt线程之间的区别。

首先,CPython线程不能并发运行,至少不是Python代码。是的,它们确实为每个Python线程创建了系统线程,但是仅允许当前持有“全局解释器锁”的线程运行(C扩展名和FFI代码可能会绕过它,但是当线程不持有GIL时不执行Python字节码)。

另一方面,我们有Qt线程,它们基本上是系统线程上的通用层,没有全局解释器锁定,因此能够并行运行。我不确定PyQt如何处理它,但是除非您的Qt线程调用Python代码,否则它们应该能够并发运行(可能会在各种结构中实现的各种额外锁)。

为了进行额外的微调,您可以修改在切换GIL所有权之前解释的字节码指令的数量-较低的值意味着更多的上下文切换(并且可能会有更高的响应度),但是每个单独线程的性能较低(如果您需要尝试切换每条指令,这对提高速度没有帮助。)

希望它可以帮助您解决问题:)


7
在这里要注意很重要:PyQt QThreads 确实具有Global Interpreter Lock所有 Python代码都会锁定GIL,并且您在PyQt中运行的所有QThread都将运行Python代码。(如果不这样做,您实际上并没有使用PyQt的“ Py”部分:)。如果您选择将该Python代码推迟到外部C库中,则GIL将被释放,但这是事实,无论您使用Python线程还是Qt线程。
夸克

实际上,这就是我试图传达的信息,所有Python代码都获得了锁定,但是对于在单独线程中运行的C / C ++代码而言,这并不重要
2009年

0

我无法评论Python和PyQt线程之间的确切差异,但我一直在使用尝试做您想做的事情QThreadQNetworkAcessManager并确保QApplication.processEvents()在线程运行时调用它。如果GUI响应确实是您要解决的问题,则稍后的内容会有所帮助。


1
QNetworkAcessManager不需要线程或processEvents。它使用异步IO操作。
卢卡什·拉林斯基2009年

哎呀......是的,我使用的组合QNetworkAcessManagerhttplib2。我的异步代码使用httplib2
brianz
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.