原文地址::https://blog.csdn.net/hechao3225/article/details/53033993


一、直接使用QMutex进行同步

创建线程方法:继承自QThread,重写void run()函数,调用成员start()启动线程,start()中可加入优先级参数。

互斥锁同步方法:void run()函数中使用QMutex来实现同步,当多个线程访问共享变量时,应使用lock/trylock和unlock将对共享变量的操作代码包裹,以保证同步访问共享变量。(C++中引起线程安全的共享资源只有两种:全局变量和静态变量)

示例代码中两个Thread均继承自QThread(),为了保证互斥锁对两个线程均可见,QMutex在一个线程CPP文件中定义,另一个线程文件做extern声明。

示例代码如下:

thread.h

[cpp] view plain copy
  1. #ifndef MYTHREAD_H  
  2. #define MYTHREAD_H  
  3. #include <QtCore>  
  4. #include <QMutex>  
  5.   
  6. class MyThread:public QThread  
  7. {  
  8. public:  
  9.     MyThread(QString name);  
  10.     void run();  
  11. private:  
  12.     QString mName;  
  13.   
  14. };  
  15.   
  16. #endif // MYTHREAD_H  

thread.cpp

[cpp] view plain copy
  1. #include "mythread.h"  
  2. #include <QDebug>  
  3.   
  4. int i=50;  
  5. QMutex mutex;  
  6. MyThread::MyThread(QString name):QThread(),mName(name)  
  7. {  
  8.     qDebug()<<"creating.."<<endl;  
  9. }  
  10.   
  11. void MyThread::run()  
  12. {  
  13.     qDebug()<<this->mName<<"running.."<<endl;  
  14.     mutex.lock();  
  15.     /* 
  16.     for(;i<100;i++) 
  17.     { 
  18.         qDebug()<<this->mName<<i<<endl; 
  19.  
  20.     } 
  21.     */  
  22.     i++;  
  23.     i*=2;  
  24.     qDebug()<<this->mName<<i<<endl;  
  25.     mutex.unlock();  
  26.   
  27.     qDebug()<<this->mName<<"stop running.."<<endl;  
  28.     sleep(1);  
  29.   
  30. }  

thread2.h

[cpp] view plain copy
  1. #ifndef MYTHREAD2_H  
  2. #define MYTHREAD2_H  
  3. #include <QThread>  
  4. #include <QMutex>  
  5. class MyThread2:public QThread  
  6. {  
  7. public:  
  8.     MyThread2(QString name);  
  9.     void run();  
  10. private:  
  11.     QString mName;  
  12.   
  13. };  
  14.   
  15. #endif // MYTHREAD2_H  

thread2.cpp

[cpp] view plain copy
  1. #include "mythread2.h"  
  2. #include <QDebug>  
  3.   
  4. extern int i;  
  5. extern QMutex mutex;  
  6. MyThread2::MyThread2(QString name):QThread(),mName(name)  
  7. {  
  8.  qDebug()<<"creating.."<<endl;  
  9. }  
  10.   
  11. void MyThread2::run()  
  12. {  
  13.     qDebug()<<this->mName<<"running.."<<endl;  
  14.     mutex.lock();  
  15.     /* 
  16.     for(;i>0;i--) 
  17.     { 
  18.         qDebug()<<this->mName<<i<<endl; 
  19.     } 
  20.     */  
  21.     i--;  
  22.     i/=2;  
  23.     qDebug()<<this->mName<<i<<endl;  
  24.     mutex.unlock();  
  25.     qDebug()<<this->mName<<"stop runnning.."<<endl;  
  26.     sleep(1);  
  27. }  

main.cpp

[cpp] view plain copy
  1. #include <QCoreApplication>  
  2. #include "mythread.h"  
  3. #include "mythread2.h"  
  4.   
  5. int main(int argc, char *argv[])  
  6. {  
  7.     QCoreApplication a(argc, argv);  
  8.     MyThread thread1("thread1");  
  9.   
  10.     MyThread2 thread2("thread2");  
  11.   
  12.     thread1.start(QThread::HighestPriority);//高优先级的任务获得更多的CPU使用比,因此先计算完成  
  13.     thread2.start();//相对低优先级的任务会后完成  
  14.   
  15.     thread1.wait();  
  16.     qDebug()<<"thread1 is done!"<<endl;  
  17.     thread1.wait();  
  18.     qDebug()<<"thread2 is done!"<<endl;  
  19.   
  20. //    thread1.exit();  
  21.      return a.exec();  
  22. }  

实验结果:

Qt多线程基础(一)线程同步之互斥锁同步


二、使用互斥锁类QMutexLocker(浅谈RAII)


问题:使用QMutex的上锁、解锁操作直接同步会有一个致命缺陷:当代码提前退出时(如函数中多处return或C++抛出异常),可能并未执行unlock(),若其他线程采用lock()阻塞式上锁会一直被阻塞等待释放,导致资源泄露。

解决:根据RAII的思想,我们应该尽量使用对象管理资源,构造时获取互斥锁,析构时释放锁。(参见Effective C++条款13)

具体来讲,QMutexLocker作为一个便利类,可以解决以下两种函数有多个出口的情况:

(1)第一种情况是函数内部多次return,如果直接使用QMutex上锁,必须保证每个return之前都及时释放锁资源(每个return前都要加上unlock()),否则当前线程的run()退出时另一个线程的run()无法获取锁,造成死锁。如下例所示:

[cpp] view plain copy
  1. int complexFunction(int flag)  
  2. {  
  3.     QMutexLocker locker(&mutex);  
  4.   
  5.     int retVal = 0;  
  6.   
  7.     switch (flag) {  
  8.     case 0:  
  9.     case 1:  
  10.         return moreComplexFunction(flag);  
  11.     case 2:  
  12.         {  
  13.             int status = anotherFunction();  
  14.             if (status < 0)  
  15.                 return -2;  
  16.             retVal = status + flag;  
  17.         }  
  18.         break;  
  19.     default:  
  20.         if (flag > 10)  
  21.             return -1;  
  22.         break;  
  23.     }  
  24.   
  25.     return retVal;  
  26. }  
可以看到如果使用QMutex进行上锁,在线程的run()函数中调用该函数,一旦该函数在中途return,又没有及时调用unlock()就会导致互斥锁永远没有机会释放。除非在每一个return前加上QMutex的unlock()。

如果我们按上述代码所示,使用QMutexLocker管理QMutex,由于函数中的QMutexLocker是一个局部对象,因此return的时候一定会调用析构并在析构内部完成互斥锁的释放。

(2)另一种情况是C++抛出异常的情况:C++标准里明确规定抛出异常时仍能保证局部对象的析构调用,这也是RAII技术的保证。也就是说由于QMutexLocker是局部对象,所以一旦遇到函数退出时,局部对象被释放都会调用析构,析构内部会释放锁。(参见Effective C++条款29)

至于为何C++抛出异常时仍能保证释放局部对象(栈上变量),这是C++标准规定,请参看:

https://segmentfault.com/q/1010000002498987


因此,Qt提供了互斥锁类QMutexLocker,当QMutexLocker作为局部对象时,函数中途return或抛出异常时均会调用析构释放对象,而该类的析构函数内部调用了参数绑定的QMutex对应的unlock()函数,这也是RAII技术的基础保证。空口无凭,如图为证,这是QMutexLocker内部的析构函数实现:

Qt多线程基础(一)线程同步之互斥锁同步

可以看到,析构里面调用unlock()函数,而unlock()函数内部调用mutex()->unlock(),mutex()是一个常量函数,返回QMutexLocker绑定的QMutex,因此mutex()->unlock()调用了QMutex的unlock()函数,完成了对互斥锁的解锁。如图:

Qt多线程基础(一)线程同步之互斥锁同步

可以看到上方的QMutex类中将QMutexLocker声明成了QMutex的友元!因此QMutexLocker可以在析构中调用QMutex的unlock()完成锁资源的释放。这就使得当run()函数有多个出口退出时(多处return或抛出异常),析构被调用并及时完成互斥锁的释放,从而避免锁资源的泄露问题。

另外一个用到RAII思想的技术比如C++STL的智能指针,也是为了避免堆上空间未及时释放的情况。如果使用普通指针申请堆空间,函数中途抛出异常(比如另一个指针申请空间失败,抛出bad_alloc异常),那该指针申请的空间将无法释放,有人说使用捕获异常在catch中释放所有资源,比如此处泄露的内存,但这并不是个好办法,于是根据RAII思想,智能指针产生了,当智能指针的引用计数减为0时会释放这块内存(delete)。

看到这里,终于放心了吗?这是为什么Qt也推荐使用QMutexLocker的原因:RAII技术可以让我们写出异常安全的代码


相关文章:

  • 2021-11-14
  • 2021-08-30
  • 2018-12-13
  • 2021-11-18
  • 2021-11-18
  • 2019-07-18
  • 2021-11-18
  • 2018-12-09
猜你喜欢
  • 2021-11-18
  • 2021-09-19
  • 2020-03-22
  • 2021-11-18
  • 2021-07-15
  • 2018-09-02
  • 2020-06-12
相关资源
相似解决方案