Qt同步线程(QMutex QMutexLocker QReadWriteLock QSemaphore QWaitCondition )
Qt同步线程
我们知道,多线程有的时候是很有用的,但是在访问一些公共的资源或者数据时,需要进行同步,否则会使数据遭到破坏或者获取的值不正确。Qt提供了一些类来实现线程的同步,如QMutex,QMutexLocker,QReadWriteLock,QReadLocker,QWriteLocker,QSemaphore和QWaitCondition。下面我们分别来看它们的用法:
QMutex
首先,简单的了解一下QMutex提供的函数。
构造函数:QMutex ( RecursionMode mode = NonRecursive )。
需要注意的是构造函数的参数,RecursionMode 递归模式。枚举类型RecursionMode 有两个值:
QMutex::Recursive,在这个模式下,一个线程可以多次锁同一个互斥量。需要注意的是,调用lock()多少次锁,就必须相应的调用unlock()一样次数解锁。
QMutex::NonRecursive(默认),在这个模式下,一个线程只能锁互斥量一次。
void QMutex::lock ()
该函数用来锁住一个互斥量。如果另外的线程已经锁住了互斥量,函数将被阻塞等待另外的线程解锁互斥量。
如果是一个可递归的互斥量,则可以从同一个线程多次调用这个函数,如果是非递归的互斥量,多次调用这个函数将会引发死锁。我们来看看源码是怎么实现的。
- void QMutex::lock()
- {
- QMutexPrivate *d = static_cast<QMutexPrivate*>(this->d);
- Qt::HANDLE self;
- if(d->recursive) {
- self = QThread::currentThreadId();
- if(d->owner == self) {
- ++d->count; //同一个线程多次lock时,仅仅自增count
- //当然递归次数太多也会导致栈溢出
- Q_ASSERT_X(d->count != , "QMutex::lock", "Overflowin recursion counter");
- return;
- }
- bool isLocked = d->contenders.testAndSetAcquire(, );
- if(!isLocked) {
- // didn'tget the lock, wait for it
- isLocked = d->wait();
- Q_ASSERT_X(isLocked, "QMutex::lock",
- "Internalerror, infinite wait has timed out.");
- }
- d->owner = self; //递归模式时,owner记录拥有互斥量的线程
- ++d->count; //记录lock的次数
- Q_ASSERT_X(d->count != , "QMutex::lock", "Overflowin recursion counter");
- return;
- }
- //非递归模式时,
- bool isLocked = d->contenders.testAndSetAcquire(, ); //尝试加锁
- if(!isLocked) {
- lockInternal(); //加锁失败则在lockInternal()中一直等到别的线程解锁。
- }
- }
- 看看lockInternal的实现
- void QMutex::lockInternal()
- {
- 。。。
- do {
- 。。。。//其他代码太复杂,感觉最重要的就是这个while循环了,
- //一直循环检测,试图加锁。这我们就好理解,非递归模式的//互斥量,不要在同一个线程里,多次调用lock了。因为第二次调用的时候会在
- //这里死循环了
- } while(d->contenders != || !d->contenders.testAndSetAcquire(, ));
- 。。。。。。。
- }
bool QMutex::tryLock ()
该函数试图锁一个互斥量,如果成功则返回true。如果另外的线程已经锁住了互斥量,函数直接返回false。
bool QMutex::tryLock ( int timeout )
该函数跟上面的trylock()相似。不同的是,如果互斥量在别的线程锁住的情况下,函数会等待timeout 毫秒。需要注意的是,如果传入的timeout 为负数,函数将无限期等待,跟调用lock()一样的效果。这个函数跟上面的差不多,所以只看该函数的源码实现就好了。
- bool QMutex::tryLock(inttimeout)
- {
- QMutexPrivate *d = static_cast<QMutexPrivate*>(this->d);
- Qt::HANDLE self;
- if(d->recursive) {
- self = QThread::currentThreadId();
- if(d->owner == self) {
- ++d->count;
- Q_ASSERT_X(d->count != , "QMutex::tryLock", "Overflow in recursion counter");
- return true;
- }
- boolisLocked = d->contenders.testAndSetAcquire(, );
- if(!isLocked) {
- // didn'tget the lock, wait for it
- isLocked = d->wait(timeout); //尝试加锁失败则等待
- if(!isLocked)
- return false;
- }
- d->owner = self;
- ++d->count;
- Q_ASSERT_X(d->count != , "QMutex::tryLock", "Overflow in recursion counter");
- return true;
- }
- //尝试加锁失败,(d->contenders.testAndSetAcquire(0,1)返回false,所以继续执行d->wait(timeout);
- return (d->contenders.testAndSetAcquire(, ) ||d->wait(timeout));
- }
- //在win下,wait函数实际上是用事件对象实现的
- bool QMutexPrivate::wait(inttimeout)
- {
- if(contenders.fetchAndAddAcquire() == ) {
- // lockacquired without waiting
- return true;
- }
- // 当timeout 小于0,则等待时间为INFINITE,这也就是为什么传负数参数时跟lock一样会无限期等待了
- boolreturnValue = (WaitForSingleObject(event,timeout < ? INFINITE : timeout) == WAIT_OBJECT_0);
- contenders.deref();
- returnreturnValue;
- }
void QMutex::unlock ()
该函数对互斥量进行解锁。如果在另外的线程加锁,尝试在别的线程进行解锁则会引发错误。试图对没有加锁的互斥量解锁结果是未定义的。
QMutexLocker
QmutexLocker只是为了简化我们对互斥量的加锁和解锁操作。就像智能指针方便我们使用普通指针一样。
QMutexLocker (QMutex * mutex )。
构造函数必须传入一个互斥量指针,然后在构造函数里mutex直接调用lock()。
- inline explicitQMutexLocker(QMutex *m)
- {
- Q_ASSERT_X((reinterpret_cast<quintptr>(m)& quintptr(1u)) == quintptr(),
- "QMutexLocker","QMutex pointer is misaligned");
- if (m){
- m->lockInline(); // mutex调用lock()加锁
- val = reinterpret_cast<quintptr>(m)| quintptr(1u);
- } else{
- val = ;
- }
- }
- inline ~QMutexLocker() { unlock(); }
- inline void unlock()
- {
- if((val & quintptr(1u)) == quintptr(1u)) {
- val &= ~quintptr(1u);
- mutex()->unlockInline(); //析构时调用unlock,确保mutex在离开调用线程时被解锁。
- }
- }
下面来看看具体的用法:
假设有个函数有很多return 语句,那么我们就必须记得在每个语句前unlock互斥量,否则互斥量将无法得到解锁,导致其他等待的线程无法继续执行。
- int complexFunction(intflag)
- {
- mutex.lock();
- int retVal = ;
- switch (flag) {
- case :
- case1:
- retVal = moreComplexFunction(flag);
- break;
- case :
- {
- int status = anotherFunction();
- if (status < ) {
- mutex.unlock();
- return -;
- }
- retVal = status + flag;
- }
- break;
- default:
- if (flag > ) {
- mutex.unlock();
- return -;
- }
- break;
- }
- mutex.unlock();
- return retVal;
- }
这样的代码显得很冗余又容易出错。如果我们用QMutexLocker
- intcomplexFunction(int flag)
- {
- QMutexLocker locker(&mutex);
- int retVal = ;
- switch (flag) {
- case :
- case :
- return moreComplexFunction(flag);
- case :
- {
- int status = anotherFunction();
- if (status < )
- return -;
- retVal = status + flag;
- }
- break;
- default:
- if (flag > )
- return -;
- break;
- }
- return retVal;
- }
由于locker 是局部变量,在离开函数作用域时,mutex肯定会被解锁。
QreadWriteLock
QreadWriteLock是一个读写锁,主要用来同步保护需要读写的资源。当你想多个读线程可以同时读取资源,但是只能有一个写线程操作资源,而其他线程必须等待写线程完成时,这时候用这个读写锁就很有用了。QreadWriteLock也有递归和非递归模式之分。
我们主要来看看最重要的两个函数是如何实现读写操作的同步的。
void QReadWriteLock::lockForRead ()
该函数lock接了读操作的锁。如果有别的线程已经对lock接了写操作的锁,则函数会阻塞等待。
- void QReadWriteLock::lockForRead()
- {
- QMutexLocker lock(&d->mutex);
- Qt::HANDLE self = ;
- if(d->recursive) {
- self = QThread::currentThreadId();
- QHash<Qt::HANDLE, int>::iterator it = d->currentReaders.find(self);
- if (it!= d->currentReaders.end()) {
- ++it.value();
- ++d->accessCount;
- Q_ASSERT_X(d->accessCount >, "QReadWriteLock::lockForRead()",
- "Overflowin lock counter");
- return;
- }
- }
- // accessCount 小于0说明有写线程在操作资源,则阻塞
- while(d->accessCount < || d->waitingWriters) {
- ++d->waitingReaders; //自增等待的读线程数
- d->readerWait.wait(&d->mutex);
- --d->waitingReaders;
- }
- if(d->recursive)
- d->currentReaders.insert(self, );
- ++d->accessCount; //自增,记录有多少个线程访问了资源
- Q_ASSERT_X(d->accessCount > , "QReadWriteLock::lockForRead()", "Overflow in lock counter");
- }
- void QReadWriteLock::lockForWrite ()
- 该函数给lock加了写操作的锁,如果别的线程已经加了读或者写的锁,则函数会被阻塞。
- void QReadWriteLock::lockForWrite()
- {
- QMutexLocker lock(&d->mutex);
- Qt::HANDLE self = ;
- if(d->recursive) {
- self = QThread::currentThreadId();
- if(d->currentWriter == self) {
- --d->accessCount;
- Q_ASSERT_X(d->accessCount <, "QReadWriteLock::lockForWrite()",
- "Overflowin lock counter");
- return;
- }
- }
- // accessCount不等于0,说明有线程在操作资源,则函数阻塞等待。
- // accessCount大于0说明有读线程在读取资源,
- // accessCount小于0说明有写线程在写数据
- while(d->accessCount != ) {
- ++d->waitingWriters; //自增等待的写线程数
- d->writerWait.wait(&d->mutex);
- --d->waitingWriters;
- }
- if(d->recursive)
- d->currentWriter = self;
- --d->accessCount;
- Q_ASSERT_X(d->accessCount < , "QReadWriteLock::lockForWrite()", "Overflow in lock counter");
- }
void QReadWriteLock::unlock ()
解锁函数,下面我们看看源码是如何实现,让等待的写线程优先于读线程获得互斥量的锁的。
- void QReadWriteLock::unlock()
- {
- QMutexLocker lock(&d->mutex);
- Q_ASSERT_X(d->accessCount != , "QReadWriteLock::unlock()", "Cannot unlock an unlocked lock");
- boolunlocked = false;
- if(d->accessCount > ) {
- // releasinga read lock
- if(d->recursive) {
- Qt::HANDLE self =QThread::currentThreadId();
- QHash<Qt::HANDLE, int>::iterator it =d->currentReaders.find(self);
- if(it != d->currentReaders.end()) {
- if(--it.value() <= )
- d->currentReaders.erase(it);
- }
- }
- // d->accessCount 说明没有线程在操作资源了unlocked为true
- unlocked = --d->accessCount == ;
- } else if (d->accessCount < &&++d->accessCount == )
- {
- // d->accessCount <0 说明有写线程在操作。则解锁unlocked = true;
- // released awrite lock
- unlocked = true;
- d->currentWriter = ;
- }
- //最重要的就是这里
- if(unlocked) {
- if(d->waitingWriters) {
- //如果有写线程在等待,则wake一个写线程。前面我们已经知道,写线程是只
- //能有一个对资源进行操作的,所以就wakeone了。
- d->writerWait.wakeOne();
- } else if (d->waitingReaders) {
- //如果没有等待的写线程,则wake全部的读线程。因为读线程是可以多个对资源进行操作的。
- d->readerWait.wakeAll();
- }
- }
- }
下面是我自己简单的实现用例:
- class Lock:publicQObject
- {
- Q_OBJECT
- public:
- Lock();
- ~Lock();
- void Start();
- void Read();
- void Write();
- void ReadThread1();
- void ReadThread2();
- void WriteThread1();
- void WriteThread2();
- protected:
- private:
- string strResource;
- QReadWriteLock lock;
- };
- Lock::Lock()
- {
- strResource = "Hellworld ......";
- }
- Lock::~Lock()
- {
- }
- void Lock::Read()
- {
- cout<<"Readdata :"<<strResource<<endl;
- QEventLoop loop;
- QTimer::singleShot(,&loop,SLOT(quit()));
- loop.exec();
- }
- void Lock::Write()
- {
- strResource = "writelock ";
- cout<<"Writedata :"<<strResource<<endl;
- QEventLoop loop;
- QTimer::singleShot(,&loop,SLOT(quit()));
- loop.exec();
- }
- void Lock::ReadThread1()
- {
- lock.lockForRead();
- cout<<"ReadThread1 lockForRead"<<endl;
- Read();
- cout<<"ReadThread1 unlock"<<endl;
- lock.unlock();
- }
- void Lock::ReadThread2()
- {
- lock.lockForRead();
- cout<<"ReadThread2 lockForRead"<<endl;
- Read();
- cout<<"ReadThread2 unlock"<<endl;
- lock.unlock();
- }
- void Lock::WriteThread1()
- {
- lock.lockForWrite();
- cout<<"WriteThread1 lockForWrite"<<endl;
- Write();
- cout<<"WriteThread1 unlock"<<endl;
- lock.unlock();
- }
- void Lock::WriteThread2()
- {
- lock.lockForWrite();
- cout<<"WriteThread2 lockForWrite"<<endl;
- Write();
- cout<<"WriteThread2 unlock"<<endl;
- lock.unlock();
- }
- void Lock::Start()
- {
- QtConcurrent::run(this,&Lock::ReadThread1);
- QtConcurrent::run(this,&Lock::ReadThread2);
- QtConcurrent::run(this,&Lock::WriteThread1);
- QtConcurrent::run(this,&Lock::WriteThread2);
- }
这里我先启动两个读线程,再启动写线程,运行结果如下。我们发现先读线程1先加了锁,读线程1还没解锁的时候,读线程2已经加了锁,验证了读线程是可以同时进入的。
如果我改一下代码:
void Lock::Start()
{
QtConcurrent::run(this,&Lock::WriteThread1);
QtConcurrent::run(this,&Lock::ReadThread1);
QtConcurrent::run(this,&Lock::ReadThread2);
QtConcurrent::run(this,&Lock::WriteThread2);
}
我先启动WriteThread1,然后启动两个读线程,最后启动WriteThread2。运行结果如下,我们发现,WriteThread1运行完之后,先运行WriteThread2,最后才是两个读线程。验证了写线程比读线程先获得锁。
QSemaphore
QSemaphore是提供一个计数的信号量。信号量是泛化的互斥量。一个信号量只能锁一次,但是我们可以多次获得信号量。信号量可以用来同步保护一定数量的资源。
信号量支持两个基本是函数, acquire()和 release():
acquire(n) :尝试获取n个资源。如果没有足够的可用资源,该函数调用会被则是。
release(n) :释放n个资源。
它们的源码实现也很简单:
- void QSemaphore::acquire(intn)
- {
- Q_ASSERT_X(n >= , "QSemaphore::acquire", "parameter 'n' must be non-negative");
- QMutexLocker locker(&d->mutex);
- while (n> d->avail) //申请的资源n 大于可用资源avail则进入等待。
- d->cond.wait(locker.mutex());
- d->avail -= n;
- }
- void QSemaphore::release(intn)
- {
- Q_ASSERT_X(n >= , "QSemaphore::release", "parameter 'n' must be non-negative");
- QMutexLocker locker(&d->mutex);
- d->avail += n;
- d->cond.wakeAll();
- }
由于avail变量,实际就是一个int的计数变量 。所以我们在调用release()传入的参数n大于信号量初始值也没关系,只是说明可用资源增加了。
例如以下代码:
- int main(int argc, char *argv[])
- {
- QCoreApplication a(argc, argv);
- QSemaphore sem();
- sem.acquire();
- cout<<"acquire(5); "<<"remaindresource :"<<sem.available()<<endl;
- sem.release();
- cout<<"release(5) "<<"remaindresource :"<<sem.available()<<endl;
- sem.release();
- cout<<"release(10) "<<"remaindresource :"<<sem.available()<<endl;
- sem.acquire();
- cout<<"acquire(15); "<<"remaindresource :"<<sem.available()<<endl;
- returna.exec();
- }
信号量最著名的就是生产者与消费者的例子,以后再研究了。
QWaitCondition
QWaitCondition类提供了一个条件变量,它允许我们通知其他线程,等待的某些条件已经满足。等待QWaitCondition变量的可以是一个或多个线程。当我们用wakeOne()通知其他线程时,系统会随机的选中一个等待进行唤醒,让它继续运行。其实前面的信号量和读写锁内部实现都有用到QWaitCondition的。
下面我们来看这个类重要的几个函数:
ool QWaitCondition::wait ( QMutex * mutex, unsigned long time =ULONG_MAX )
该函数对mutex解锁,然后等待。在调用这个函数之前,mutex必须是加锁状态。如果mutex没有加锁,则函数直接返回。如果mutex是可递归的,函数也直接返回。该函数对mutex解锁,然后等待,知道以下条件之一满足:
1. 另外的线程调用wakeOne()或 wakeAll(),则该函数会返回true。
2. 时间过了Time毫秒。如果time为ULONG_MAX(默认),则将会一直等待不会超时。如果超时则返回false。
bool QWaitCondition::wait ( QReadWriteLock * readWriteLock, unsigned long time =ULONG_MAX )
函数对readWriteLock解锁并等待条件变量。在调用这个函数之前,readWriteLock必须是加锁状态的。如果不是加锁状态,则函数立即返回。readWriteLock必须不能是递归加锁的,否则将不能正确的解锁。返回的满足条件跟上面的函数一样。
http://blog.csdn.net/hai200501019/article/details/9889123
Qt同步线程(QMutex QMutexLocker QReadWriteLock QSemaphore QWaitCondition )的更多相关文章
- Qt同步线程(比较清楚,而且QMutex QMutexLocker QReadWriteLock QSemaphore QWaitCondition 每个都有例子)
Qt同步线程 我们知道,多线程有的时候是很有用的,但是在访问一些公共的资源或者数据时,需要进行同步,否则会使数据遭到破坏或者获取的值不正确.Qt提供了一些类来实现线程的同步,如QMutex,QMute ...
- Qt QThread 线程创建,线程同步,线程通信 实例
1. 继承QThread, 实现run()方法, 即可创建线程. 2. 实例1 代码 myThread.h #ifndef MYTHREAD_H #define MYTHREAD_H #includ ...
- Qt 控制线程的顺序执行(使用QWaitCondition,并且线程类的run函数里记得加exec(),使得线程常驻)
背景项目中用到多线程,对线程的执行顺序有要求: A.一个线程先收数据 B.一个线程处理数据 C.一个线程再将处理后的数据发送出去 要求三个线程按照ABC的顺序循环执行. 思路子类化多线程方法 重写子类 ...
- Qt经典—线程、事件与Qobject(耳目一新)
介绍 You’re doing it wrong. — Bradley T. Hughes 线程是qt channel里最流行的讨论话题之一.许多人加入了讨论并询问如何解决他们在运行跨线程编程时所遇到 ...
- Qt经典—线程、事件与Qobject
介绍 You’re doing it wrong. — Bradley T. Hughes 线程是qt channel里最流行的讨论话题之一.许多人加入了讨论并询问如何解决他们在运行跨线程编程时所遇到 ...
- Linux多线程——使用互斥量同步线程
前文再续,书接上一回,在上一篇文章: Linux多线程——使用信号量同步线程中,我们留下了一个如何使用互斥量来进行线程同步的问题,本文将会给出互斥量的详细解说,并用一个互斥量解决上一篇文章中,要使用两 ...
- [转]QT子线程与主线程的信号槽通信-亲测可用!
近用QT做一个服务器,众所周知,QT的主线程必须保持畅通,才能刷新UI.所以,网络通信端采用新开线程的方式.在涉及到使用子线程更新Ui上的控件时遇到了点儿麻烦.网上提供了很多同一线程不同类间采用信号槽 ...
- Qt 的线程与事件循环
Qt 的线程与事件循环
- 线程:主线程、子线程 同步线程、异步线程 单线程、多线程 System.Threading与System.Windows.Threading
入门-------------------------------------------------------------------------------- 概述与概念 一个C#程序开始 ...
随机推荐
- docker深入学习一
docker是一个客户服务器结构的应用程序,其结构如下所示 其组成部分包括 container容器:是image的运行实例,一般container之间以及container与主机之间是相互隔离的,相当 ...
- json转义问题
后端程序接受前台传递过来json 1正常json没有问题 比如 {"id":21,"userName":"2张天师","phon ...
- nginx配置比较杂乱的总结
常用变量 demo uri www.example.com/mock/interface?param1=203¶m2=test $args uri中的参数值 ?后面的部分 param1 ...
- 状态机的Verilog写法
“硬件设计很讲究并行设计思想,虽然用Verilog描述的电路大都是并行实现的,但是对于实际的工程应用,往往需要让硬件来实现一些具有一定顺序的工作,这就要用到状态机思想.什么是状态机呢?简单的说,就是通 ...
- 如何访问Pod
本章看点: 理清Deployment,ReplicaSet和Pod的关系,以及三者之间的网络关系,ip地址和端口号 通过Pod进入docker容器修改里面的内容 外部网络访问Pod里面的应用 一.通过 ...
- c# 操作xml文件(读写)
根据项目需求,我们需要记录用户的操作痕迹,当用户下次登录操作同一个文件时,页面显示为用户上一次执行的用户轨迹,我们考虑把用户的历史记录写进xml文件中. 存储的xml数据结构: XML操作类: usi ...
- Alfred神器使用手册【转】
我曾经耗费巨大的精力,试图在计算机的使用效率上找到一条优化的捷径,一直以来都收效甚微.直到遇上 alfred,它强大的工作流机制,彻底解决了输入输出的痛点,极大的减少了程序之间的切换成本和重复按键成本 ...
- 解决点击空<a>标签返回页面顶部的问题
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- C# 利用特性(Attribute)实现通用实体类数据合法校验
用过asp.net mvc 的都应该知道,在实体类上添加一些特性,可以实现后端实体的数据校验,这里简单实现一下 实现原理:利用反射获取实体的每一个属性,并通过属性获取属性上标注的特性,调用特性的Val ...
- Flink源码阅读(一)——Flink on Yarn的Per-job模式源码简析
一.前言 个人感觉学习Flink其实最不应该错过的博文是Flink社区的博文系列,里面的文章是不会让人失望的.强烈安利:https://ververica.cn/developers-resource ...