关于多线程:在PyQt应用程序中进行线程化:使用Qt线程还是Python线程?

Threading in a PyQt application: Use Qt threads or Python threads?

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

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

无论如何,应用程序使用pyqt4,所以我想知道更好的选择是:使用qt的线程还是使用python threading模块?各有哪些优点/缺点?或者你有完全不同的建议?

edit(re-boundy):在我的特定情况下,解决方案可能会使用非阻塞网络请求,如jeff ober和luk_?Lalinsky建议(基本上把并发问题留给网络实现),我仍然希望对一般问题有一个更深入的答案:

使用pyqt4(即qt)线程与本机python线程(来自threading模块)相比有哪些优点和缺点?

编辑2:谢谢大家的回答。尽管没有100%的一致意见,但似乎普遍认为答案是"使用qt",因为它的优点是与库的其余部分集成,而不会造成真正的缺点。

对于任何想要在两个线程实现之间进行选择的人,我强烈建议他们阅读这里提供的所有答案,包括Abbot链接到的Pyqt邮件列表线程。

我为赏金考虑了几个答案;最后,我选择了方丈的,作为非常相关的外部参考;然而,这是一个非常接近的要求。

再次感谢。


这在不久前的Pyqt邮件列表中讨论过。引用乔瓦尼·巴乔对这个问题的评论:

It's mostly the same. The main difference is that QThreads are better
integrated with Qt (asynchrnous signals/slots, event loop, etc.).
Also, you can't use Qt from a Python thread (you can't for instance
post event to the main thread through QApplication.postEvent): you
need a QThread for that to work.

A general rule of thumb might be to use QThreads if you're going to interact somehow with Qt, and use Python threads otherwise.

Pyqt的作者早些时候对此发表了一些评论:"它们都是同一个本机线程实现的包装器"。两种实现都以相同的方式使用gil。


Python的线程将更简单、更安全,而且由于它是针对基于I/O的应用程序的,所以它们能够绕过gil。也就是说,您是否考虑过使用扭曲或非阻塞套接字/选择来实现非阻塞I/O?

编辑:有关线程的详细信息

Python线程

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

重要警告:这可能会产生误导,因为字节码指令的数量与程序中的行数不对应。在Python中,即使是一个单独的赋值也可能不是原子的,因此对于必须原子地执行的任何代码块(即使使用gil),都需要互斥锁。

QT螺纹

当python将控制权交给第三方编译的模块时,它将释放gil。模块有责任在需要时确保原子性。当控件返回时,Python将使用gil。这会使使用第三方库和线程混淆。更难使用外部线程库,因为它增加了模块与解释器之间控制在何时何地的不确定性。

qt螺纹在释放gil的情况下工作。qt线程能够同时执行qt库代码(以及其他未获取gil的编译模块代码)。但是,在qt线程上下文中执行的python代码仍然获得gil,现在您必须管理两组逻辑来锁定代码。

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

非阻塞I/O

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

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

对于基于事件的、无阻塞的I/O,一个很好的解决方案是新的Diesel库。目前它仅限于Linux,但速度非常快,而且相当优雅。

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


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

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


我在Pytalk工作时也问过自己同样的问题。

如果使用qt,则需要使用QThread才能使用qt框架,尤其是信号/时隙系统。

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

而且,这两个选择都不是一个性能问题,因为它们都是C++绑定。

这是我对pyqt和thread的经验。

我鼓励你使用QThread


杰夫有些优点。只有一个主线程可以进行任何GUI更新。如果您确实需要从线程中更新GUI,qt-4的排队连接信号使跨线程发送数据变得容易,并且在您使用qthread时会自动调用;我不确定如果您使用python线程,它们是否会被调用,尽管向connect()添加参数很容易。


我也不能推荐,但我可以尝试描述cpython和qt线程之间的区别。

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

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

对于额外的微调,您可以修改在切换gil之前解释的字节码指令的数量-较低的值意味着更多的上下文切换(可能更高的响应),但每个线程的性能较低(上下文切换有其成本-如果您尝试每隔几条指令切换一次,这没有帮助速度。)

希望它能帮助你解决问题。)


我不能评论python和pyqt线程之间的确切区别,但我一直在使用QThreadQNetworkAcessManager,并确保在线程活动时调用QApplication.processEvents()。如果您试图解决的问题是图形用户界面的响应性,那么稍后的将有所帮助。