PyQt 应用程序中的线程:使用 Qt 线程还是 Python 线程?
我正在编写一个 GUI 应用程序,它定期通过 Web 连接检索数据。由于此检索需要一段时间,这会导致 UI 在检索过程中无响应(无法将其拆分为更小的部分)。这就是为什么我想将网络连接外包给单独的工作线程。
[是的,我知道,现在我有两个问题.]
无论如何,应用程序使用 PyQt4,所以我想知道更好的选择是:使用 Qt 的线程还是使用 Python threading
模块?各自的优点/缺点是什么?或者你有完全不同的建议吗?
编辑(重新赏金):虽然我的特定情况下的解决方案可能会使用非阻塞网络请求,例如Jeff Ober 和 Lukáš Lalinský 建议(所以基本上将并发问题留给网络实现),我仍然会就像对一般问题的更深入的回答:
与本机 Python 线程(来自 threading
模块)相比,使用 PyQt4(即 Qt)线程的优点和缺点是什么?
编辑2:感谢大家的回答。尽管没有 100% 一致,但似乎存在广泛的共识,答案是“使用 Qt”,因为这样做的优点是与库的其余部分集成,同时不会造成真正的缺点。
对于任何想要在两种线程实现之间进行选择的人,我强烈建议他们阅读此处提供的所有答案,包括 PyQt 邮件列表线程 abbot 链接到。
对于赏金,我考虑了几个答案;最后我选择了 Abbot's 作为非常相关的外部参考;然而,这是一次千钧一发的机会。
再次感谢。
I'm writing a GUI application that regularly retrieves data through a web connection. Since this retrieval takes a while, this causes the UI to be unresponsive during the retrieval process (it cannot be split into smaller parts). This is why I'd like to outsource the web connection to a separate worker thread.
[Yes, I know, now I have two problems.]
Anyway, the application uses PyQt4, so I'd like to know what the better choice is: Use Qt's threads or use the Python threading
module? What are advantages / disadvantages of each? Or do you have a totally different suggestion?
Edit (re bounty): While the solution in my particular case will probably be using a non-blocking network request like Jeff Ober and Lukáš Lalinský suggested (so basically leaving the concurrency problems to the networking implementation), I'd still like a more in-depth answer to the general question:
What are advantages and disadvantages of using PyQt4's (i.e. Qt's) threads over native Python threads (from the threading
module)?
Edit 2: Thanks all for you answers. Although there's no 100% agreement, there seems to be widespread consensus that the answer is "use Qt", since the advantage of that is integration with the rest of the library, while causing no real disadvantages.
For anyone looking to choose between the two threading implementations, I highly recommend they read all the answers provided here, including the PyQt mailing list thread that abbot links to.
There were several answers I considered for the bounty; in the end I chose abbot's for the very relevant external reference; it was, however, a close call.
Thanks again.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
Python的线程会更简单、更安全,而且由于它是针对基于I/O的应用程序,因此它们能够绕过GIL。也就是说,您是否考虑过使用 Twisted 或非阻塞套接字/选择来实现非阻塞 I/O?
编辑:有关线程的更多信息
Python 线程
Python 的线程是系统线程。然而,Python 使用全局解释器锁 (GIL) 来确保解释器一次只执行一定大小的字节码指令块。幸运的是,Python 在输入/输出操作期间释放 GIL,使线程可用于模拟非阻塞 I/O。
重要警告:这可能会产生误导,因为字节码指令的数量不与程序中的行数相对应。在 Python 中,即使是单个赋值也可能不是原子的,因此对于任何必须以原子方式执行的代码块(即使使用 GIL)来说,互斥锁都是必需的。
QT 线程
当 Python 将控制权交给第 3 方编译的模块时,它会释放 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 库的包装器,它提供了基于事件的编程的基本框架,使用适合您的系统的最快可用方法(在编译时确定)。
Python's threads will be simpler and safer, and since it is for an I/O-based application, they are able to bypass the GIL. That said, have you considered non-blocking I/O using Twisted or non-blocking sockets/select?
EDIT: more on threads
Python threads
Python's threads are system threads. However, Python uses a global interpreter lock (GIL) to ensure that the interpreter is only ever executing a certain size block of byte-code instructions at a time. Luckily, Python releases the GIL during input/output operations, making threads useful for simulating non-blocking I/O.
Important caveat: This can be misleading, since the number of byte-code instructions does not correspond to the number of lines in a program. Even a single assignment may not be atomic in Python, so a mutex lock is necessary for any block of code that must be executed atomically, even with the GIL.
QT threads
When Python hands off control to a 3rd party compiled module, it releases the GIL. It becomes the responsibility of the module to ensure atomicity where required. When control is passed back, Python will use the GIL. This can make using 3rd party libraries in conjunction with threads confusing. It is even more difficult to use an external threading library because it adds uncertainty as to where and when control is in the hands of the module vs the interpreter.
QT threads operate with the GIL released. QT threads are able to execute QT library code (and other compiled module code that does not acquire the GIL) concurrently. However, the Python code executed within the context of a QT thread still acquires the GIL, and now you have to manage two sets of logic for locking your code.
In the end, both QT threads and Python threads are wrappers around system threads. Python threads are marginally safer to use, since those parts that are not written in Python (implicitly using the GIL) use the GIL in any case (although the caveat above still applies.)
Non-blocking I/O
Threads add extraordinarily complexity to your application. Especially when dealing with the already complex interaction between the Python interpreter and compiled module code. While many find event-based programming difficult to follow, event-based, non-blocking I/O is often much less difficult to reason about than threads.
With asynchronous I/O, you can always be sure that, for each open descriptor, the path of execution is consistent and orderly. There are, obviously, issues that must be addressed, such as what to do when code depending on one open channel further depends on the results of code to be called when another open channel returns data.
One nice solution for event-based, non-blocking I/O is the new Diesel library. It is restricted to Linux at the moment, but it is extraordinarily fast and quite elegant.
It is also worth your time to learn pyevent, a wrapper around the wonderful libevent library, which provides a basic framework for event-based programming using the fastest available method for your system (determined at compile time).
QThread
的优点是它与 Qt 库的其余部分集成。也就是说,Qt 中的线程感知方法需要知道它们在哪个线程中运行,并且要在线程之间移动对象,您需要使用 QThread。另一个有用的功能是在线程中运行您自己的事件循环。如果您正在访问 HTTP 服务器,则应该考虑 QNetworkAccessManager。
The advantage of
QThread
is that it's integrated with the rest of the Qt library. That is, thread-aware methods in Qt will need to know in which thread they run, and to move objects between threads, you will need to useQThread
. Another useful feature is running your own event loop in a thread.If you are accessing a HTTP server, you should consider
QNetworkAccessManager
.当我使用 PyTalk 时,我问了自己同样的问题。
如果您使用 Qt,则需要使用 QThread 才能使用 Qt 框架,特别是信号/槽系统。
使用信号/槽引擎,您将能够从一个线程到另一个线程以及项目的每个部分进行对话。
此外,由于两者都是 C++ 绑定,因此此选择不存在很大的性能问题。
这是我对 PyQt 和线程的经验。
我鼓励您使用
QThread
。I asked myself the same question when I was working to PyTalk.
If you are using Qt, you need to use
QThread
to be able to use the Qt framework and expecially the signal/slot system.With the signal/slot engine, you will be able to talk from a thread to another and with every part of your project.
Moreover, there is not very performance question about this choice since both are a C++ bindings.
Here is my experience of PyQt and thread.
I encourage you to use
QThread
.杰夫有一些好的观点。只有一个主线程可以执行任何 GUI 更新。如果您确实需要从线程内更新 GUI,Qt-4 的 排队连接信号使跨线程发送数据变得容易,并且如果您使用QThread,则会自动调用;我不确定如果您使用 Python 线程,它们是否会是这样,尽管向
connect()
添加参数很容易。Jeff has some good points. Only one main thread can do any GUI updates. If you do need to update the GUI from within the thread, Qt-4's queued connection signals make it easy to send data across threads and will automatically be invoked if you're using QThread; I'm not sure if they will be if you're using Python threads, although it's easy to add a parameter to
connect()
.我也不能真正推荐,但我可以尝试描述 CPython 和 Qt 线程之间的差异。
首先,CPython 线程不会并发运行,至少 Python 代码不会。是的,它们确实为每个 Python 线程创建系统线程,但是只允许当前持有全局解释器锁的线程运行(C 扩展和 FFI 代码可能会绕过它,但当线程不持有 GIL 时,Python 字节码不会被执行)。
另一方面,我们有 Qt 线程,它们基本上是系统线程上的公共层,没有全局解释器锁,因此能够并发运行。我不确定 PyQt 如何处理它,但是除非您的 Qt 线程调用 Python 代码,否则它们应该能够并发运行(禁止可能在各种结构中实现的各种额外锁)。
为了进行额外的微调,您可以修改在切换 GIL 所有权之前解释的字节码指令的数量 - 较低的值意味着更多的上下文切换(并且可能更高的响应能力),但每个单独线程的性能较低(上下文切换有其成本 - 如果您尝试每隔几条指令切换一次,这对速度没有帮助。)
希望它能帮助解决您的问题:)
I can't really recommend either, but I can try describing differences between CPython and Qt threads.
First of all, CPython threads do not run concurrently, at least not Python code. Yes, they do create system threads for each Python thread, however only the thread currently holding Global Interpreter Lock is allowed to run (C extensions and FFI code might bypass it, but Python bytecode is not executed while thread doesn't hold GIL).
On the other hand, we have Qt threads, which are basically common layer over system threads, don't have Global Interpreter Lock, and thus are capable of running concurrently. I'm not sure how PyQt deals with it, however unless your Qt threads call Python code, they should be able to run concurrently (bar various extra locks that might be implemented in various structures).
For extra fine-tuning, you can modify the amount of bytecode instructions that are interpreted before switching ownership of GIL - lower values mean more context switching (and possibly higher responsiveness) but lower performance per individual thread (context switches have their cost - if you try switching every few instructions it doesn't help speed.)
Hope it helps with your problems :)
我无法评论 Python 和 PyQt 线程之间的确切差异,但我一直在使用
QThread
、QNetworkAcessManager
执行您尝试执行的操作,并确保当线程处于活动状态时调用QApplication.processEvents()
。如果 GUI 响应能力确实是您要解决的问题,I can't comment on the exact differences between Python and PyQt threads, but I've been doing what you're attempting to do using
QThread
,QNetworkAcessManager
and making sure to callQApplication.processEvents()
while the thread is alive. If GUI responsiveness is really the issue you're trying to solve, the later will help.这是不久前在 PyQt 邮件列表中讨论的。引用 Giovanni Bajo 对此主题的评论:
PyQt 作者之前对此主题的一些评论:“它们都是相同本机线程实现的包装器”。两种实现都以相同的方式使用 GIL。
This was discussed not too long ago in PyQt mailing list. Quoting Giovanni Bajo's comments on the subject:
And some earlier comment on this subject from PyQt's author: "they are both wrappers around the same native thread implementations". And both implementations use GIL in the same way.