问题:PyQt应用程序中的线程:使用Qt线程还是Python线程?
我正在编写一个GUI应用程序,该应用程序通过Web连接定期检索数据。由于此检索需要一段时间,因此会导致UI在检索过程中无响应(无法拆分成较小的部分)。这就是为什么我想将Web连接外包给单独的工作线程。
[是的,我知道,现在我有两个问题。]
无论如何,该应用程序使用PyQt4,所以我想知道更好的选择是:使用Qt的线程还是使用Python threading
模块?各自的优点/缺点是什么?还是您有完全不同的建议?
编辑(赏金):虽然在我的特定情况下,解决方案可能会使用非阻塞网络请求,例如Jeff Ober和LukášLalinský建议的(所以基本上将并发性问题留给了网络实现),但我仍然想要更多深入回答一般问题:
与本地Python线程(来自threading
模块)相比,使用PyQt4(即Qt)线程有什么优缺点?
编辑2:谢谢大家的回答。尽管没有达成100%的协议,但似乎普遍的共识是答案是“使用Qt”,因为这样做的优点是可以与库的其余部分集成,而不会造成任何实际的缺点。
对于希望在这两种线程实现之间进行选择的任何人,我强烈建议他们阅读此处提供的所有答案,包括方丈链接到的PyQt邮件列表线程。
我考虑了一些悬赏的答案;最后,我选择了方丈作为非常相关的外部参考;然而,这是一个密切的电话。
再次感谢。
回答 0
不久前在PyQt邮件列表中对此进行了讨论。引用乔凡尼·巴霍(Giovanni Bajo)对这个问题的评论:
大致相同。主要区别在于QThreads与Qt(异步信号/插槽,事件循环等)更好地集成在一起。另外,您不能在Python线程中使用Qt(例如,不能通过QApplication.postEvent将事件发布到主线程):您需要一个QThread才能工作。
一般的经验法则是,如果您要以某种方式与Qt进行交互,则可以使用QThreads;否则,请使用Python线程。
PyQt的作者对此主题有较早的评论:“它们都是相同的本机线程实现的包装器”。两种实现都以相同的方式使用GIL。
回答 1
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库的包装器,它为系统使用最快的可用方法(在编译时确定)提供了基于事件的编程的基本框架。
回答 2
优点QThread
是它与Qt库的其余部分集成在一起。也就是说,Qt中的线程感知方法将需要知道它们在哪个线程中运行,并且需要在线程之间移动对象QThread
。另一个有用的功能是在线程中运行您自己的事件循环。
如果要访问HTTP服务器,则应考虑QNetworkAccessManager
。
回答 3
在PyTalk上工作时,我问了同样的问题。
如果您使用的是Qt,则需要使用QThread
能够使用Qt框架,尤其是信号/插槽系统。
使用信号/插槽引擎,您将能够从一个线程与另一个线程以及项目的每个部分进行对话。
而且,由于这两者都是C ++绑定,因此对于此选择没有太大的性能问题。
这是我对PyQt和线程的经验。
我鼓励你使用QThread
。
回答 4
杰夫有一些优点。只有一个主线程可以执行任何GUI更新。如果确实需要从线程内更新GUI,则Qt-4的排队连接信号使跨线程发送数据变得容易,并且如果使用QThread,则将自动调用该信号。我不确定您是否正在使用Python线程,尽管可以轻松地向中添加参数connect()
。
回答 5
我也不能真正推荐,但是我可以尝试描述CPython和Qt线程之间的区别。
首先,CPython线程不能并发运行,至少不是Python代码。是的,它们确实为每个Python线程创建了系统线程,但是仅允许当前持有“全局解释器锁”的线程运行(C扩展名和FFI代码可能会绕过它,但是当线程不持有GIL时不执行Python字节码)。
另一方面,我们有Qt线程,它们基本上是系统线程上的通用层,没有全局解释器锁定,因此能够并行运行。我不确定PyQt如何处理它,但是除非您的Qt线程调用Python代码,否则它们应该能够并发运行(可能会在各种结构中实现的各种额外锁)。
为了进行额外的微调,您可以修改在切换GIL所有权之前解释的字节码指令的数量-较低的值意味着更多的上下文切换(并且可能会有更高的响应度),但是每个单独线程的性能较低(如果您需要尝试切换每条指令,这对提高速度没有帮助。)
希望它可以帮助您解决问题:)
回答 6
我无法评论Python和PyQt线程之间的确切差异,但我一直在使用尝试做您想做的事情QThread
,QNetworkAcessManager
并确保QApplication.processEvents()
在线程运行时调用它。如果GUI响应确实是您要解决的问题,则稍后的内容会有所帮助。