线程的目的是为了并行的执行代码,有时线程需要互相等待。例如,如果两个线程同时写一个变量,执行结果是未定义的。因此对共享资源的需要进行“同步”处理。Qt提供了低级别的、高级别的机制处理线程同步。
低级别的线程同步
- QMutex互斥量:为了访问共享资源,线程会锁定互斥量。其他的线程必须等该线程释放互斥量时,才可访问共享资源。
- QReadWriteLock读写锁:区分对共享数据的“读”和“写”。多个线程可以同时读取共享数据,但是只有一个线程写共享数据。
- QSemaphore信号量:保护若干数量的共享资源。常用于生产消费者的循环队列。
- QWaitCondition等待条件:使信号处于等待状态,直到特定的条件被触发。可通过wakeOne()/wakeAll()唤醒其他线程。
上述同步类可以保证函数的线程安全。但是为了保证函数线程安全,会有性能的损耗,这也是为什么Qt部分函数不是线程安全的原因。
潜在风险
如果一个线程对资源加锁,但是没有释放锁,这个程序就有可能被冻结(因为该资源对于其他线程而言就是不能处理的)。例如:在函数内部抛出了异常并退出,但是在退出前并没有释放锁。
另外的场景就是死锁。例如:线程A等待线程B释放资源,但是线程B也在等待线程A释放资源,因此线程互相等待中(发生死锁)。
便利类
为了避免上述潜在风险第一种场景,Qt提供了Locker便利类:在构造函数中对资源加锁(调用lock),在析构函数中对资源释放锁(调用unLock)。
- QMutex-QMutexLocker
- QReadWriteLock-QReadLocker/QWriterLocker
高级别的线程同步
Qt的事件系统在线程通信中非常有用。每个线程都有独自的线程循环,为了在另外一个线程中调用槽函数,调用时机应该在目标线程的事件循环中。为了在事件循环中放置调用时机,应采用队列的信号槽连接方式。当发送信号时,它的参数会被记录到事件系统中,信号接收者所在的线程会执行槽函数。
不像使用低级别的线程同步原语,使用事件系统处理线程同步,不会有死锁的风险。但是,事件系统不强制处理互斥,如果调用方访问了共享资源,仍然需要使用低级别的线程同步原语保护共享资源。