Qt 线程(04):同步线程【官翻】

同步线程

前言

尽管线程的目的是允许代码并行运行,但有时线程必须停止并等待其他线程。 例如,如果两个线程试图同时写入同一变量,则结果是不确定的。 强制线程互相等待的原理称为互斥。 这是保护共享资源(如数据)的常用技术。

Qt提供了低级原语以及用于同步线程的高级机制。

低级同步原语

QMutex是强制互斥的基本类。线程锁定互斥锁是为了访问共享资源。如果另一个线程试图锁定已经锁定的互斥锁,那么第二个线程将处于睡眠状态,直到第一个线程完成其任务并解锁互斥锁。

QReadWriteLock类似于QMutex,除了可以区分“读”和“写”访问。当一个数据没有被写入时,多个线程同时从它读取是安全的。QMutex强制多个读取器轮流读取共享数据,而QReadWriteLock允许同步读取,从而提高并行性。

QSemaphore是QMutex的泛化,它保护一定数量的相同资源。相反,QMutex只保护一个资源。信号量示例(Semaphores Example)展示了信号量的一个典型应用:在生产者和消费者之间同步访问循环缓冲区。

QWaitCondition不是通过强制互斥,而是通过提供条件变量来同步线程。其他原语使线程等待,直到资源被解锁,而QWaitCondition则使线程等待,直到满足特定的条件。为了允许等待的线程继续执行,可以调用wakeOne()来唤醒一个随机选择的线程,或者调用wakeAll()来同时唤醒它们。等待条件示例(Wait Conditions Example)展示了如何使用QWaitCondition而不是QSemaphore来解决生产者-消费者问题。

注意:Qt的同步类依赖于使用正确对齐的指针。例如,您不能在MSVC中使用打包类。

这些同步类可用于使方法线程安全。但是,这样做会导致性能损失,这就是为什么大多数Qt方法没有实现线程安全。

风险

如果一个线程锁定了一个资源但没有对其进行解锁,那么应用程序可能会冻结,因为其他线程将永久无法使用该资源。例如,如果抛出异常并强制当前函数返回而不释放其锁,就会发生这种情况。

一个类似的场景是死锁。例如,假设线程A正在等待线程B解锁一个资源。如果线程B也在等待线程A解锁另一个资源,那么两个线程将会一直等待下去,因此应用程序将会冻结。

便捷类

QMutexLocker, QReadLockerQWriteLocker都是方便类,它们使QMutex和QReadWriteLock的使用更加容易。它们在构造资源时锁定资源,在析构资源时自动解锁资源。它们被设计用来简化使用QMutex和QReadWriteLock的代码,从而减少意外地永久锁定资源的机会。

高级事件队列

Qt的事件系统对于线程间通信非常有用。每个线程都可能有自己的事件循环。要在另一个线程中调用一个slot(或任何可调用的方法),请将该调用放在目标线程的事件循环中。这允许目标线程在槽槽开始运行之前完成当前任务,而原始线程继续并行运行。

要在事件循环中放置调用,请创建一个排队的信号插槽连接。无论何时发出信号,它的参数都将被事件系统记录。信号接收器所在的线程将运行这个槽。或者,调用QMetaObject::invokeMethod()以在不使用信号的情况下实现同样的效果。在这两种情况下,都必须使用排队连接,因为直接连接绕过事件系统并立即在当前线程中运行该方法。

与使用低级原语不同,在使用事件系统进行线程同步时不存在死锁的风险。但是,事件系统不强制互斥。如果可调用方法访问共享数据,它们仍然必须使用低级原语保护。

话虽如此,Qt的事件系统以及隐式共享的数据结构提供了传统线程锁定的替代方案。如果信号和插槽是独家使用的,并且线程之间没有共享变量,那么一个多线程程序可以完全不使用低级原语。

请参阅QThread::exec()、线程和QObjects。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值