Qt:同步线程
尽管线程的目的是允许代码并行运行,但有时线程必须停止并等待其他线程。比如,如果两个线程 尝试同时写入同一个变量,则结果是不确定的。强制线程相互等待的原理称为互斥。这是保护共享资源(如数据)的常用技术。
Qt提供了低级原语以及用于同步线程的高级机制
低级同步原语
QMutex
是强制执行互斥的基本类。线程锁定互斥锁以获取对共享资源的访问。如果第二个线程试图在已锁定互斥锁的同时锁定它,则第二个线程将进入睡眠状态,直到第一个线程完成其任务并解锁该互斥锁
QReadWriteLock
与QMutex类似,不同之处在于它区分了“读”和“写”访问。当数据块没有被写入时,多个线程同时从中读取数据是安全的。QMutex强制多个读取器轮流读取共享数据,而QReadWriteLock允许同时读取数据,从而提高了并行性。
QSemaphore
是QMutex的泛化,它保护一定数量的相同资源。相反,QMutex只保护一个资源。信号量示例展示了信号量的一个典型应用:在生产者和消费者之间同步访问循环缓冲区。
QWaitCondition
不是通过强制互斥,而是通过提供一个条件变量来同步线程。其他原语让线程等待,直到资源被解锁,而QWaitCondition则让线程等待,直到满足特定条件。为了让等待的线程继续运行,可以调用wakeOne()来唤醒随机选择的一个线程,或者调用wakeAll()来同时唤醒所有线程。等待条件示例展示了如何使用QWaitCondition而不是QSemaphore来解决生产者-消费者问题。
注意:Qt的同步类依赖于使用正确对齐的指针。例如,不能在MSVC中使用打包类。
这些同步类可以用来使方法线程安全。然而,这样做会导致性能损失,这就是为什么大多数Qt方法不是线程安全的原因。
风险
如果一个线程锁定了一个资源但没有解锁它,应用程序可能会冻结,因为该资源将对其他线程永久不可用。例如,如果抛出异常并强制当前函数返回而不释放其锁,就会发生这种情况。
另一个类似的场景是死锁。例如,假设线程A正在等待线程B解锁一个资源。如果线程B也在等待线程A解锁不同的资源,那么两个线程都将永远等待,因此应用程序将冻结。
方便类
QMutexLocker,QReadLocker
和QWriteLocker
是方便使用的类,它们使得使用QMutex和QReadWriteLock更容易。它们在构建时锁定资源,在销毁时自动解锁资源。它们的设计目的是简化使用QMutex和QReadWriteLock的代码,从而减少因意外而永久锁定资源的机会。
高级事件队列
Qt的事件系统对于线程间通信非常有用。每个线程都可能有自己的事件循环。要在另一个线程中调用插槽(或任何可调用方法),请将该调用放置在目标线程的事件循环中。这让目标线程在插槽开始运行之前完成当前任务,而原始线程继续并行运行。
要将调用置于事件循环中,请建立排队的信号槽连接。每当发出信号时,事件系统都会记录其自变量。信号接收器所在的线程将运行该插槽。或者,调用QMetaObject :: invokeMethod()
以达到没有信号的相同效果。在这两种情况下,都必须使用排队连接,因为直接连接会绕过事件系统并立即在当前线程中运行该方法。
使用事件系统进行线程同步时,与使用低级原语不同,没有死锁的风险。但是,事件系统不强制执行互斥。如果可调用方法访问共享数据,则仍必须使用低级原语对其进行保护。
话虽如此,Qt的事件系统以及隐式共享的数据结构为传统线程锁定提供了一种替代方法。如果仅使用信号和插槽,并且线程之间没有共享变量,那么多线程程序可以完全不使用低级原语。
另请参见:QThread::exec()
、Threads and QObjects.
还没有评论,来说两句吧...