)
目录1. 互斥锁2. 条件变量3. 信号量4. 读写锁5. 死锁多个线程同时访问共享数据时可能会冲突出现意料之外的结果这源于操作的原子性问题关于操作原子性可问度娘这里不多做解释。保证线程间同步的方法有cQt互斥锁QMute条件变量QWaitCondition信号量QSemaphore读写锁QReadLocker、QWriteLocker、QReadWriteLock1. 互斥锁QMutex使用场景多个线程同时访问共享数据时可能发生冲突基本功能上锁、解锁且同一时间只能有一个线程上锁使用mutex.lock()和mutex.unlock()来锁住两个以上的资源块这样CPU在进行线程调度切换到这些资源块时会判断互斥锁mutex是否能上锁若能则上锁并执行该资源块否则进入阻塞状态将执行权交由其它线程等下次调度到该线程再次判断是否能上锁按此规律直到获取到锁或进入死锁状态。另外还有trylock(int time0)函数该函数用于尝试获取锁并可以设置未获取到锁的等待时间与lock()不同的是trylock()函数不会使线程一直阻塞而lock()函数的超时间是无穷大即未获取到锁会一直阻塞我认为这是死锁的最重要因素关于死锁后面会介绍其它操作类QMutexLocker提供便捷的加解锁操作在其创建时加锁析构时自动解锁详细如下。int complexFunction(int flag) { QMutexLocker locker(mutex); //加锁只需要由mutex创建一个QMutexLocker对象 int retVal 0; switch (flag) { case 0: case 1: return moreComplexFunction(flag); //1 case 2: { int status anotherFunction(); if (status 0) return -2; //2 retVal status flag; } break; default: if (flag 10) return -1; //3 break; } return retVal; //4 //四处returnlocker都会调用析构从而解锁不用考虑复杂的解锁逻辑 }2. 条件变量QWaitCondition使用场景线程A需要等某个条件成立才能继续往下执行基本功能允许一个线程告诉其它线程某个条件已经满足线程A需要等某个条件成立才能继续往下执行现在这个条件不成立线程A就阻塞等待而线程B在执行过程中使这个条件成立了就唤醒线程A继续执行。关键函数wait和wakeAllwakeOne这里理解wait的作用和使用前先加锁是关键bool QWaitCondition::wait(QMutex *lockedMutex, unsigned long time ULONG_MAX)作用释放锁并在wake前等待超时时间。这里的锁可以是QMutex也可以是QReadWriteLock;使用前需要先lock否则无法释放也就满足不了使用条件达不到使用目的bool QWaitCondition::wait(QReadWriteLock *lockedReadWriteLock, unsigned long time ULONG_MAX)void QWaitCondition::wakeAll()唤醒条件变量正在等待的条件变量wait等到唤醒状态可继续执行void QWaitCondition::wakeOne()使用//全局代码 QMutex mutex; QCondition consumerCondition; // QCondition producerCondition; // //线程consumer代码 mutex.lock() consumerCondition.wait(mutex); // 释放锁并等待producer唤起待producerCondition调用wake后满足条件继续执行后面代码;当前线程会阻塞直到其它线程调用wakeOne()或wakeAll()其中wakeOne()随机唤醒某一线程wakeAll()唤醒所有线程完整代码示例如下// 全局代码 QMutex mutex; QWaitCondition consumerCondition; QWaitCondition producerCondition; int buffer 0; const int MAX_SIZE 10; bool running true; // 生产者线程 void producer() { while (running) { mutex.lock(); while (buffer MAX_SIZE) { qDebug() Producer: Buffer full, waiting...; producerCondition.wait(mutex); } buffer; qDebug() Producer: Produced, buffer buffer; consumerCondition.wakeAll(); mutex.unlock(); QThread::msleep(100); // 模拟生产耗时 } } // 消费者线程 void consumer() { while (running) { mutex.lock(); while (buffer 0) { qDebug() Consumer: Buffer empty, waiting...; consumerCondition.wait(mutex); } buffer--; qDebug() Consumer: Consumed, buffer buffer; producerCondition.wakeAll(); mutex.unlock(); QThread::msleep(150); // 模拟消费耗时 } }3. 信号量semaphore基本功能上锁、解锁同一时间能有多个线程上锁直到达到允许的上限mutex相当于semaphore的特例函数说明acquire(int n)获取资源数n如果n可用资源数该函数将阻塞否则成功获取同时资源数减nrelease(int n)释放资源数navaliable() const :int 获取当前剩余资源数tryAcquire(int n, int timeout0) : bool尝试获取资源数n若获取不到等待timeout时间后返回false该函数不同于acquire(),不会导致阻塞代码示例QSemaphore sem(5); // 创建资源数为5的信号量 sem.acquire(3); // 获取资源数3还剩2 sem.acquire(2); // 获取资源数2还剩0此后若再获取会阻塞直到有资源释放 sem.release(5); // 释放资源数5还剩5 sem.release(5); // 释放资源数5还剩10 sem.tryAcquire(1); // 尝试获取资源数1成功还剩9return true sem.tryAcquire(200); // 尝试获取资源数200失败还剩9return false4. 读写锁使用场景如果共享数据是只读的那么各线程读到的数据应该总是一致的不会出现冲突。只要有一个线程可以改写数据就必须考虑线程间同步的问题。由此引出了读写锁Reader-Writer Lock的概念Reader之间并不互斥可以同时读共享数据而Writer是独占的在Writer修改数据时其他Reader或Writer不能访问数据因此Reader-Wirter Lock必Mutex具有更好的并发性。5. 死锁以下是造成死锁的四个必要条件1、互斥一个资源每次只能被一个线程使用即一旦资源被某个线程访问了其它线程不能再访问除非访问结束2、占有并等待一个进程因请求资源而阻塞时对已获得的资源保持不放3、不可剥夺: 其它线程已获得的资源再其未主动释放前你不能强行剥夺占有权4、循环等待: 存在一组线程每个线程都占有下一个线程所需要的至少一个资源死锁会导致程序长时间处于阻塞状态影响电脑性能。造成死锁的条件也是解决死锁的方法条件1非共享资源是线程需要同步的原因所以不能从这里考虑条件2可以将所有需要的资源在一个同步锁内申请完这样多个嵌套锁不存在也就不会死锁但这样会降低系统的并发行条件3在进程因请求资源阻塞时释放现有资源即可解决死锁问题。在代码中可以通过将lock改为trylock并在trylock失败后释放已获取的资源并重新获取解决条件4如果需要锁个锁都能按照相同该顺序获得锁则不会出现死锁备注写程序时应该尽量避免同时获得多个锁如果一定有必要这么做则有一个原则如果所有线程在需要多个锁时都按相同的先后顺序常见的是按Mutex变量的地址顺序获得锁则不会出现死锁。比如一个程序中用到锁1、锁2、锁3它们所对应的Mutex变量的地址是锁1锁2锁3那么所有线程在需要同时获得2个或3个锁时都应该按锁1、锁2、锁3的顺序获得。如果要为所有的锁确定一个先后顺序比较困难则应该尽量使用trylock调用代替lock调用以免死锁。