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

bal*_*pha 107 python multithreading pyqt

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

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

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

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

使用PyQt4(即Qt)线程优于本机Python线程(来自threading模块)的优点和缺点是什么?


编辑2:谢谢大家的答案.虽然没有100%的协议,但似乎普遍认为答案是"使用Qt",因为它的优点是与库的其余部分集成,同时没有造成任何真正的缺点.

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

我考虑了赏金的几个答案; 最后,我选择了abbot作为非常相关的外部参考; 然而,这是一个近距离的电话.

再次感谢.

abb*_*bot 101

不久前在PyQt邮件列表中讨论过这个问题.引用Giovanni Bajo 对此主题的评论:

它大致相同.主要区别在于QThreads与Qt(异步信号/时隙,事件循环等)更好地集成.此外,您不能使用Python线程中的Qt(例如,您不能通过QApplication.postEvent将事件发布到主线程):您需要一个QThread来实现它.

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

还有一些早先对PyQt作者对此主题的评论:"它们都是围绕相同本机线程实现的包装器".两种实现都以相同的方式使用GIL.

  • 很好的答案,但我认为你应该使用blockquote按钮清楚地显示你实际上没有总结,但从邮件列表中引用Giovanni Bajo :) (2认同)
  • 我想知道为什么你不能通过QApplication.postEvent()将事件发布到主线程并且需要一个QThread呢?我想我已经看到有人这样做,而且有效. (2认同)
  • 鉴于此问题和答案的投票率很高,我认为值得指出 [ekhumoro 最近的 SO 答案](/sf/answers/3486180491/),其中详细说明了它的条件从 Python 线程使用某些 Qt 方法是安全的。这与我和@Trilarion 所观察到的行为相符。 (2认同)

Jef*_*ber 32

Python的线程将更简单,更安全,并且由于它适用于基于I/O的应用程序,因此它们可以绕过GIL.也就是说,您是否考虑过使用Twisted或非阻塞套接字/ select的非阻塞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,现在你必须要管理2台逻辑的锁定你的代码.

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

非阻塞I/O.

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

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

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

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

  • 实际上没有什么绕过GIL。但是Python在I / O操作期间释放了GIL。当“移交给”已编译的模块时,Python还会释放GIL,这些模块负责获取/释放GIL本身。 (2认同)
  • 更新是错误的.Python代码在Python线程中的运行方式与在QThread中完全相同.当您运行Python代码(然后Python管理线程之间的执行)时,您将获取GIL,并在运行C++代码时释放它.完全没有区别. (2认同)

Luk*_*ský 21

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

如果您正在访问HTTP服务器,则应该考虑QNetworkAccessManager.


Nat*_*tim 13

当我在PyTalk工作时,我问自己同样的问题.

如果您使用的是Qt,则需要QThread使用Qt框架,特别是信号/插槽系统.

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

此外,由于两者都是C++绑定,因此没有关于此选择的性能问题.

这是我对PyQt和线程的体验.

我鼓励你使用QThread.


Kal*_*son 9

杰夫有一些好处.只有一个主线程可以执行任何GUI更新.如果你确实需要从线程内更新GUI,Qt-4的排队连接信号可以很容易地跨线程发送数据,如果你正在使用QThread,它将自动被调用; 我不确定他们是否会使用Python线程,尽管添加参数很容易connect().


p_l*_*p_l 5

我也不能真正推荐,但我可以尝试描述CPython和Qt线程之间的差异.

首先,CPython线程不会同时运行,至少不是Python代码.是的,他们确实为每个Python线程创建了系统线程,但是只允许当前持有Global Interpreter Lock的线程运行(C扩展和FFI代码可能绕过它,但是当线程不保持GIL时不执行Python字节码).

另一方面,我们有Qt线程,它们基本上是系统线程上的公共层,没有Global Interpreter Lock,因此能够并发运行.我不确定PyQt如何处理它,但是除非你的Qt线程调用Python代码,否则它们应该能够并发运行(禁止可能在各种结构中实现的各种额外锁).

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

希望它有助于你的问题:)

  • 这里需要注意的是:PyQt QThreads*确实采用Global Interpreter Lock*._All_ Python代码锁定GIL,你在PyQt中运行的任何QThread都将运行Python代码.(如果他们不是,你实际上并没有使用PyQt的"Py"部分:).如果您选择将该Python代码推迟到外部C库中,那么GIL将被释放,但无论您使用的是Python线程还是Qt线程,都是如此. (6认同)