线程基础

什么是线程?

线程是并行地做事情,仅仅像进程。那么,线程与进程有何不同?当在电子表格上计算时,可能还有媒体播放器在同一台式机上运行,播放您最喜爱的歌曲。这是并行工作的 2 个进程的范例:一个运行电子表格程序;另一个运行媒体播放器。为此,多任务处理是众所周知的术语。仔细观察媒体播放器会发现,在一个单进程中还有事情在并行。当媒体播放器将音乐发送给音频驱动器时,带有所有铃声和哨子的用户界面还在不断更新。这就是线程 -- 在一个单进程内并发。

那么,如何实现并发呢?在单核 CPU 上并行工作是一种错觉,有点类似于电影中移动图像的错觉。对于进程,错觉是在很短时间后中断处理器在一进程中的工作而产生的。然后,处理器继续处理下一进程。为在进程之间切换,保存当前程序计数器,并加载下一处理器的程序计数器。这还不够,因为需要对寄存器、某些体系结构及特定 OS 数据执行相同操作。

就像一个 CPU 可以驱动 2 个或多个进程,也可以让 CPU 运行在一个单进程的 2 个不同代码段上。当进程启动时,它总是执行一代码段,因此进程被称为拥有一线程。不管怎样,程序可能决定启动第 2 线程。然后,在一个进程内同时处理 2 个不同的代码序列。通过重复保存程序计数器和寄存器,然后加载下一线程的程序计数器和寄存器,在单核 CPU 上达成并发。在活动线程之间循环,不需要程序的合作。当切换到下一线程发生时,线程可能处于任何状态。

CPU 设计的当前趋势是拥有多个核心。典型的单线程应用程序只能使用一个核心。不管怎样,可以将具有多个线程的程序赋值给多个核心,从而使事情以真正的并发方式发生。结果,将工作分发给多个线程可以使程序在多核 CPU 上运行得更快,因为可以使用其它核心。

GUI 线程和工作线程

As mentioned, each program has one thread when it is started. This thread is called the "main thread" (also known as the "GUI thread" in Qt applications). The Qt GUI must run in this thread. All widgets and several related classes, for example QPixmap , don't work in secondary threads. A secondary thread is commonly referred to as a "worker thread" because it is used to offload processing work from the main thread.

同时访问数据

Each thread has its own stack, which means each thread has its own call history and local variables. Unlike processes, threads share the same address space. The following diagram shows how the building blocks of threads are located in memory. Program counter and registers of inactive threads are typically kept in kernel space. There is a shared copy of the code and a separate stack for each thread.

"Thread visualization"

If two threads have a pointer to the same object, it is possible that both threads will access that object at the same time and this can potentially destroy the object's integrity. It's easy to imagine the many things that can go wrong when two methods of the same object are executed simultaneously.

Sometimes it is necessary to access one object from different threads; for example, when objects living in different threads need to communicate. Since threads use the same address space, it is easier and faster for threads to exchange data than it is for processes. Data does not have to be serialized and copied. Passing pointers is possible, but there must be a strict coordination of what thread touches which object. Simultaneous execution of operations on one object must be prevented. There are several ways of achieving this and some of them are described below.

So what can be done safely? All objects created in a thread can be used safely within that thread provided that other threads don't have references to them and objects don't have implicit coupling with other threads. Such implicit coupling may happen when data is shared between instances as with static members, singletons or global data. Familiarize yourself with the concept of thread safe and reentrant classes and functions.

使用线程

There are basically two use cases for threads:

  • Make processing faster by making use of multicore processors.
  • Keep the GUI thread or other time critical threads responsive by offloading long lasting processing or blocking calls to other threads.

何时使用线程替代

Developers need to be very careful with threads. It is easy to start other threads, but very hard to ensure that all shared data remains consistent. Problems are often hard to find because they may only show up once in a while or only on specific hardware configurations. Before creating threads to solve certain problems, possible alternatives should be considered.

Alternative Comment
QEventLoop::processEvents () 调用 QEventLoop::processEvents () repeatedly during a time-consuming calculation prevents GUI blocking. However, this solution doesn't scale well because the call to processEvents() may occur too often, or not often enough, depending on hardware.
QTimer Background processing can sometimes be done conveniently using a timer to schedule execution of a slot at some point in the future. A timer with an interval of 0 will time out as soon as there are no more events to process.
QSocketNotifier QNetworkAccessManager QIODevice::readyRead () This is an alternative to having one or multiple threads, each with a blocking read on a slow network connection. As long as the calculation in response to a chunk of network data can be executed quickly, this reactive design is better than synchronous waiting in threads. Reactive design is less error prone and energy efficient than threading. In many cases there are also performance benefits.

In general, it is recommended to only use safe and tested paths and to avoid introducing ad-hoc threading concepts. The QtConcurrent module provides an easy interface for distributing work to all of the processor's cores. The threading code is completely hidden in the QtConcurrent framework, so you don't have to take care of the details. However, QtConcurrent can't be used when communication with the running thread is needed, and it shouldn't be used to handle blocking operations.

应使用哪种 Qt 线程技术?

Qt 中的多线程技术 page for an introduction to the different approaches to multithreading to Qt, and for guidelines on how to choose among them.

Qt 线程基础

The following sections describe how QObjects interact with threads, how programs can safely access data from multiple threads, and how asynchronous execution produces results without blocking a thread.

QObject 和线程

As mentioned above, developers must always be careful when calling objects' methods from other threads. Thread affinity does not change this situation. Qt documentation marks several methods as thread-safe. postEvent() is a noteworthy example. A thread-safe method may be called from different threads simultaneously.

In cases where there is usually no concurrent access to methods, calling non-thread-safe methods of objects in other threads may work thousands of times before a concurrent access occurs, causing unexpected behavior. Writing test code does not entirely ensure thread correctness, but it is still important. On Linux, Valgrind and Helgrind can help detect threading errors.

保护数据完整性

When writing a multithread application, extra care must be taken to avoid data corruption. See 同步线程 for a discussion on how to use threads safely.

处理异步执行

One way to obtain a worker thread's result is by waiting for the thread to terminate. In many cases, however, a blocking wait isn't acceptable. The alternative to a blocking wait are asynchronous result deliveries with either posted events or queued signals and slots. This generates a certain overhead because an operation's result does not appear on the next source line, but in a slot located somewhere else in the source file. Qt developers are used to working with this kind of asynchronous behavior because it is much similar to the kind of event-driven programming used in GUI applications.

范例

Qt comes with several examples for using threads. See the class references for QThread and QThreadPool for simple examples. See the 线程和并发编程范例 page for more advanced ones.

深入挖掘

Threading is a very complicated subject. Qt offers more classes for threading than we have presented in this tutorial. The following materials can help you go into the subject in more depth:

  • Qt 中的线程支持 document is a good starting point into the reference documentation.
  • Qt comes with several additional examples for QThread 和 QtConcurrent .
  • Several good books describe how to work with Qt threads. The most extensive coverage can be found in Advanced Qt Programming by Mark Summerfield, Prentice Hall - roughly 70 of 500 pages cover QThread and QtConcurrent .