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

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

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

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

编辑(重新赏金) : 虽然在我的特殊情况下,解决方案可能是使用非阻塞网络请求,如 Jeff OberLuká Lalinský所建议的(所以基本上把并发问题留给网络实现) ,我仍然希望对一般问题有一个更深入的答案:

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


编辑2: 感谢你们所有人的回答。虽然没有100% 的一致意见,但是答案似乎是“使用 Qt”,因为这样做的好处是与库的其他部分集成,而不会造成真正的缺点。

对于那些希望在两个线程实现之间做出选择的人,我强烈建议他们阅读这里提供的所有答案,包括 方丈链接到的 PyQt 邮件列表线程。

关于赏金,我考虑了几个答案; 最后,我选择了方丈的答案,作为非常相关的外部参考; 然而,这是一次侥幸的机会。

再次感谢。

55292 次浏览

Python 的线程将更简单、更安全,而且因为它适用于基于 I/O 的应用程序,所以它们能够绕过 GIL。也就是说,您是否考虑过使用 Twsted 或非阻塞套接字/select 实现非阻塞 I/O?

编辑: 更多关于线程

Python 线程

Python 的线程是系统线程。然而,Python 使用 GIL (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 (尽管上面的警告仍然适用)

异步多工文件描述符

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

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

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

学习 平事特也是值得的,平事特是一个包装美妙的 libevent 库的包装器,它使用系统中最快的可用方法(在编译时确定)为基于事件的编程提供了一个基本框架。

杰夫说得有道理。只有一个主线程可以执行任何 GUI 更新。如果你确实需要从线程内部更新 GUI,Qt-4的 排队连接信号使得跨线程发送数据变得容易,并且如果你使用 QThread,它会自动被调用; 如果你使用 Python 线程,我不确定它们是否会被调用,虽然它很容易添加一个参数到 connect()

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

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

我不能评论 Python 线程和 PyQt 线程之间的确切差异,但是我一直在使用 QThreadQNetworkAcessManager执行您正在尝试执行的操作,并确保在线程处于活动状态时调用 QApplication.processEvents()。如果 GUI 响应性确实是您试图解决的问题,那么 后者将会有所帮助。

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

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

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

此外,由于两者都是 C + + 绑定,因此这种选择并不存在很大的性能问题。

以下是我对 PyQt 和线程的体验。

我鼓励你使用 QThread

我也不推荐这样做,但是我可以尝试描述 CPython 和 Qt 线程之间的差异。

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

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

对于额外的微调,你可以修改在切换 GIL 所有权之前解释的字节码指令的数量——较低的值意味着更多的上下文切换(可能更高的响应能力) ,但是每个线程的性能较低(上下文切换是有代价的——如果你尝试每切换几个指令,它不会帮助提高速度)

希望对你的问题有所帮助:)

这是不久前在 PyQt 邮件列表中的 讨论过。引用 Giovanni Bajo 关于这个主题的 评论:

大体上是一样的,主要的区别是 QThreads 更好 与 Qt 集成(异步信号/插槽、事件循环等)。 另外,不能从 Python 线程中使用 Qt (例如,不能使用 Qt) 通过 QApplication.postEvent 将事件发送到主线程) : 您 需要一个 QThread 才能工作。

一般的经验法则是,如果您打算以某种方式与 Qt 交互,那么可以使用 QThreads,否则可以使用 Python 线程。

PyQt 的作者早些时候对这个主题的一些评论是: “它们都是围绕同一个本地线程实现的包装器”。两种实现都以相同的方式使用 GIL。