当前位置 博文首页 > Allen Roson:QT线程同步

    Allen Roson:QT线程同步

    作者:[db:作者] 时间:2021-06-22 21:11

    线程同步
    虽然线程的目的是允许代码并行运行,但有时线程必须停止并等待其他线程。例如,如果两个线程试图同时写入同一个变量,结果是未定义的。强制线程彼此等待的原则称为互斥。它是保护共享资源(如数据)的常用技术。
    Qt提供了用于同步线程的低级原语和高级机制。

    ?

    低级的同步原语
    QMutex是执行互斥的基本类。线程锁定互斥锁是为了获得对共享资源的访问权。如果第二个线程试图锁定互斥锁,而它已经被锁定了,那么第二个线程将处于休眠状态,直到第一个线程完成它的任务并解锁互斥锁为止。
    QReadWriteLock类似于QMutex,不同的是它区分了读和写访问。当一个数据没有被写入时,多个线程同时从它读取是安全的。QMutex强制多个读取器轮流读取共享数据,但QReadWriteLock允许同时读取,从而提高了并行性。
    QSemaphore是QMutex的泛化,它保护一定数量的相同资源。相反,QMutex只保护一个资源。Semaphores示例展示了信号量的一个典型应用:在生产者和消费者之间同步对循环缓冲区的访问。
    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。

    上一篇:没有了
    下一篇:没有了