多线程应用中,由于多个线程的存在,线程之间可能需要访问同一个变量,或者一个线程可能需要等待另外一个线程完成某个操作后才产生相应的动作。
如:线程中计算量大的几条代码段,执行较长时间,不希望在执行过程中被其他线程打断,需要保护起来,这就是线程同步的概念。
Qt中,有多个类可以实现线程同步的功能,包括QMutex、QMutexLocker、QReadWriterLock、QReadLocker、QWriterLocker、QWaitCondition、QSemaphore。
还提供了QtConcurrent高级API实现多线程编程,同时实现多线程程序可以自动根据处理器内核个数调整线程个数。
1.QMutex和QMutexLocker 是基于互斥量的线程同步类。QMutex定义的实例是一个互斥量
QMutex主要提供3个函数:
1)lock():锁定互斥量,如果另一个线程锁定该互斥量,它将阻塞执行直到其他线程解锁这个互斥量
2)unlock():解锁互斥量
3)tryLock():试图锁定一个互斥量,如果成功就返回true,如果已被其他线程锁定,返回false,但不会阻塞程序执行
//mythread类中:
private:
QMutex mutex // 信号量
将原函数中 主线程通过信号槽得到线程处理数据(线程处理 emit(value))改为
主线程不断查询读取新数据
1 run() 2 3 { 4 5 //// 6 7 if(xxx){ 8 9 mutex.lock(); 10 11 //数据处理 12 13 mutex.unlock(); 14 15 } 16 17 } 18 19 bool xxx::readValue() 20 21 { 22 23 if(mutex.tryLock()) 24 25 { 26 27 //xxxxxxxxxxxxxxxxxxx 28 29 mutex.unlock(); 30 31 return true; 32 33 } 34 35 else 36 37 return false 38 39 }
2. QMutexLocker 是简化互斥量处理的类。QMutexLocker构造函数接收一个互斥量作为参数并将其锁定,QMutexLocker的析构函数则将此互斥量解锁。在QMutexLocker实例变量生存期内的代码段得到保护,自动进入互斥量的锁定和解锁。
上文例子改为:
1 run() 2 3 { 4 5 //// 6 7 if(xxx){ 8 9 QMutexLocker Locker(&mutex); // mutex.lock(); 10 11 //数据处理 12 13 // mutex.unlock(); 14 15 } 16 17 } 18 19
3. QReadWriteLock
每次只有一个线程获得互斥量的使用权限,一个程序中如果有多个线程读取某个变量,使用互斥量时候也必须排队。若只是读取一个变量,多线程可以同时访问,否则互斥量就会降低程序性能。
QReadWriteLock以读或者写锁定的同步方法允许以读或写的方式保护一段代码,可以允许多个线程以只读方式同步访问资源,但是只要有一个线程以写方式访问资源,其他线程就必须等待直到写操作结束。
提供以下几个函数:
lockForRead(): 以只读方式锁定资源,如果有其他线程以写入方式锁定,这个函数会阻塞
lockForWrite(): 以写入方式锁定资源,如果本线程和其他线程以读或写模式锁定资源,这个函数会阻塞
unlock():解锁
tryLockForRead():lockForRead非阻塞版本
tryLockForWrite():lockForWrite非阻塞版本
例如:
1 QReadWriteLock Lock; 2 3 threadA : run() 4 5 { 6 7 Lock.lockForWrite() //B和C同时被阻塞 8 9 //写数据 10 11 Lock.unlock() 12 13 } 14 15 16 17 threadB: run() 18 19 { 20 21 Lock.lockForRead() //A无法以写入模式锁定,被阻塞 22 23 //读数据 24 25 Lock.unlock() 26 27 } 28 29 threadC : run() 30 31 { 32 33 Lock.lockForRead() 34 35 //读数据 36 37 Lock.unlock() 38 39 }
4. QReadLocker 和 QWriteLocker是 QReadWriteLock的简便形式 类似QMutexLocker是QMutex的简便版本一样。
1 QReadWriteLock Lock; 2 3 threadA : run() 4 5 { 6 7 QWriteLocer Locker(&Lock) //B和C同时被阻塞 8 9 //写数据 10 11 12 13 } 14 15 16 17 threadB: run() 18 19 { 20 21 QReadLocer Locker(&Lock) //A无法以写入模式锁定,被阻塞 22 23 //读数据 24 25 26 27 } 28 29 threadC : run() 30 31 { 32 33 QReadLocer Locker(&Lock) 34 35 //读数据 36 37 38 39 }
5.QWaitCondition 一般用于生产者/消费者(producer / consumer)模型中:生产者产生数据,消费者使用数据
多线程程序中,多个线程的同步实际就是他们之间的协调问题。
以上方案在一个线程解锁资源以后,不能及时通知其他线程
QWaitCondition提供了另外一种改进的线程同步方法:
QWaitCondition和QMutex结合,可以使得一个线程在满足一定条件时候通知其他多个线程,是他们及时作出相应,效率比只使用互斥量要高
提供以下函数:
1)wait(QMutex *lockedMutex)解锁互斥量lockedMutex,并阻塞等待唤醒条件,被唤醒后锁定lockedMutex并退出函数
2)wakeAll() 唤醒所有处于等待状态的线程,线程唤醒的顺序不确定,有操作系统的调度策略决定
3)wakeOne()唤醒一个处于等待状态的线程,唤醒哪个不确定,由操作系统的调度策略决定
1 QMutex mutex 2 3 QWaitConditon newdataAvailable 4 5 threadA::run() 6 7 { 8 9 mutex,lock() 10 11 //获取数据 12 13 mutex.unlock() 14 15 newdataAvaliable.wakeAll() //唤醒所有线程由新数据了 16 17 } 18 19 20 21 threadB::run() 22 23 { 24 25 mutex.lock() 26 27 newdataAvailable.wat(&mutex) //先解锁mutex 使得其他线程可以使用mutex 28 29 emit newValue() //读取数据,发射信号的方式把数据传递出去 30 31 mutex.unlock() 32 33 } 34 35 36 37 注意:threadConsumer.start() //应该先启动,先进入wait状态 38 39 threadProducer.start() // 应该后启动,内部wakeAll时候,threadConsumer可以及时响应,否则会丢失数据 40 41 42 43 threadProducer.stopThread() 44 45 threadProducer.wait() 46 47 threadConsumer.terminate() //可能处于等待状态 用terminate强制结束 48 49 threadConsumer,wait() 50 51
7 Semaphore 基于信号量的线程同步
Semaphore是另一种限制对共享资源进行访问的线程同步机制,与Mutex相似但是有区别
一个Mutex只能被锁定一次,信号量可以多次使用。信号量通常用来保护一定数量的相同的资源,如数据采集时的双缓冲区。
提供以下函数:
1)acquire(int n) 尝试获取n个资源,如果没有这么多资源,程序将阻塞直到有这么多资源
2)release(int n)释放n个资源,如果信号量的资源已经全部可用之后再release,就可以创建更多的资源,增加可用资源的个数
3)int available()返回当前信号量可用的资环个数,这个数永远不为负,==0 ,说明当前没有可用资源
4)bool tryAcquire(int n=1)尝试获取n个资源,不成功不阻塞线程
QSemaphore WC(5) //WC.available() = 5
WC.acquire(4) //WC.available() = 1
WC.release(2) //WC.available() = 3
WC.acquire(3) //WC.available() = 0
WC.tryAcquire(1) //WC.available() = 0 返回 false
WC.acquire() //WC.available() = 0 没有可用资源 阻塞
n个资源就是信号量需要保护的共享资源,至于资源如何分配,就是内部处理的问题
例:
双缓冲区模拟数据采集卡
1 //共享变量区域: 2 3 const int BufferSize = BUF_SIZE 4 5 int buf1 [BufferSize] 6 7 int buf2 [BufferSize] 8 9 int CurBuf = 1 // 当前正在写入的Buffer 10 11 int bufNo = 0 //采集的数据区序号 12 13 14 15 QSemaphore emptyBufs[2] //信号量 空的缓冲区个数 初始资源个数为2 16 17 QSemaphore fullBufs //信号量 满的缓冲区个数 初始资源个数为0 18 19 20 21 22 23 QThreadDA::run() //数据采集线程 24 25 { 26 27 m_stop = false ; //启动线程时 m_stop = false 28 29 bufNo = 0 ; //缓冲区序号 30 31 curBuf = 1 //当前写入使用的缓冲区 32 33 int n = emptyBufs.available(); 34 35 if(n < 2) 36 37 emptyBufs.release(2-n) //保证线程启动的时候empty.avaliable == 2 38 39 while( ! m_stop ) 40 41 { 42 43 emtpyBufs.acquire(); //获取一个空缓冲区 44 45 数据产生中 for(i=0;i < BufferSize ;i++): 46 47 { if(curBuf == 1) 48 49 buf1【i】 = 新采集数据数据 50 51 else 52 53 buf2【i】 =新采集数据数据 54 55 } 56 57 bufNo++; 58 59 if(curBuf == 1) 60 61 curBuf = 2 62 63 else 64 65 curBuf = 1 66 67 fullBufs.releasse() //有了一个满的缓冲区 available = 1 68 69 } 70 71 72 73 } 74 75 76 77 //实际情况下,读取现成的操作应该比采集线程的速度要快 78 79 QThreadShow::run() 80 81 { 82 83 m_stop = false 84 85 int n = fullBufs.available() 86 87 if( n > 0 ) 88 89 fullBufs.acquire(n) // 将fullBuf可用资源个数初始化为0 90 91 while(!m_stop) 92 93 { 94 95 fullBufs.acquire() //等待缓冲区满 当fullBufs.available = 0 时阻塞 96 97 int data【BufSize】 98 99 int seq = bufNo 100 101 102 103 if(curBuf == 1) //当前在写入的缓冲区是1 那么满的缓冲区是2 104 105 for(。。) 106 107 data【i】 = buf2【i】 108 109 else 110 111 112 113 for(。。) 114 115 data【i】 = buf1【i】 116 117 118 119 emptyBufs.release() //释放一个空缓冲区 120 121 emit newview(data,datasize,seq); 122 123 } 124 125 126 127 }