同步线程

虽然线程的目的是允许代码并行运行,但有时线程必须停止并等待其它线程。例如:若 2 线程试着同时写入同一变量,结果就是未定义的。强制线程互相等待的原理称为 相互排斥 。它是保护共享资源 (如:数据) 的常见技术。

Qt 为同步线程提供了低级原语及高级机制。

低级同步原语

QMutex 是实施相互排斥的基础类。线程锁定互斥以获得对共享资源的访问。若第 2 线程试着锁定已锁定的互斥,第 2 线程将进入休眠状态,直到第 1 线程完成其任务并解锁互斥。

QReadWriteLock 类似 QMutex ,除区分 Read (读取) 和 Write (写入) 访问外。当不写入数据块时,多个线程同时读取是安全的。 QMutex 强制多个读取器轮流读取共享数据,但 QReadWriteLock 允许同时读取,从而改善并行性。

QSemaphore 是一般化的 QMutex ,保护一定数量的恒等资源。相比之下, QMutex 准确保护某一资源。 信号量范例 展示信号量的典型应用程序:在生产者和消费者之间同步访问循环缓冲。

QWaitCondition synchronizes threads not by enforcing mutual exclusion but by providing a condition variable . While the other primitives make threads wait until a resource is unlocked, QWaitCondition makes threads wait until a particular condition has been met. To allow the waiting threads to proceed, call wakeOne() to wake one randomly selected thread or wakeAll() to wake them all simultaneously. The 等待条件范例 shows how to solve the producer-consumer problem using QWaitCondition 而不是 QSemaphore .

注意: Qt's synchronization classes rely on the use of properly aligned pointers. For instance, you cannot use packed classes with MSVC.

These synchronization classes can be used to make a method thread safe. However, doing so incurs a performance penalty, which is why most Qt methods are not made thread safe.

风险

If a thread locks a resource but does not unlock it, the application may freeze because the resource will become permanently unavailable to other threads. This can happen, for example, if an exception is thrown and forces the current function to return without releasing its lock.

Another similar scenario is a deadlock . For example, suppose that thread A is waiting for thread B to unlock a resource. If thread B is also waiting for thread A to unlock a different resource, then both threads will end up waiting forever, so the application will freeze.

方便类

QMutexLocker , QReadLocker and QWriteLocker are convenience classes that make it easier to use QMutex and QReadWriteLock . They lock a resource when they are constructed, and automatically unlock it when they are destroyed. They are designed to simplify code that use QMutex and QReadWriteLock , thus reducing the chances that a resource becomes permanently locked by accident.

高级事件队列

Qt 的 事件系统 is very useful for inter-thread communication. Every thread may have its own event loop. To call a slot (or any invokable method) in another thread, place that call in the target thread's event loop. This lets the target thread finish its current task before the slot starts running, while the original thread continues running in parallel.

To place an invocation in an event loop, make a queued 信号-槽 connection. Whenever the signal is emitted, its arguments will be recorded by the event system. The thread that the signal receiver 活在 will then run the slot. Alternatively, call QMetaObject::invokeMethod () to achieve the same effect without signals. In both cases, a 队列连接 must be used because a 直接连接 bypasses the event system and runs the method immediately in the current thread.

There is no risk of deadlocks when using the event system for thread synchronization, unlike using low-level primitives. However, the event system does not enforce mutual exclusion. If invokable methods access shared data, they must still be protected with low-level primitives.

Having said that, Qt's event system, along with 隐式共享 data structures, offers an alternative to traditional thread locking. If signals and slots are used exclusively and no variables are shared between threads, a multithreaded program can do without low-level primitives altogether.

另请参阅 QThread::exec () 和 线程和 QObject .