QT线程同步技术详解

一、线程同步概念

二、QT线程同步技术简介

2.1初级锁

2.2读写锁

2.3条件锁

三、QT线程同步应用详解

3.1初级锁

3.2读写锁

3.3条件锁


一、线程同步概念

        线程同步,广义上的概念一般指多线程间对资源“读”与“写”权限的管理策略,为了避免数据冲突与数据不一致,关于此概念,网上介绍很多,在此不赘述。

二、QT线程同步技术简介

2.1初级锁

        对需要保护的资源上锁,独占式使用,如:公厕的蹲坑,一次只能一人用,你进入了,你就会反锁,别人就进不去,这就是初级的独占式上锁。

        QT提供了相关的类QMutex,有lock和unlock函数,成对使用。

2.2读写锁

        为了更高效的利用资源,QT提供了读写权限分离锁QReadWriteLock,有以下4种应用场景:

        A:我在读的时候,你也可以读:一起看看无所谓,又不会看坏;

        B:我在读的时候,你不可以写:我读的时候,你不能动手,以免我看得眼花缭乱;

        C:我在写的时候,你不可以读:我动手时,你别看,以免你看不真切;

        D:我在写的时候,你不可以写:我动手时,你可别动手,搞不清楚到底谁动的手;

        以上,写的时候必须独占,读的时候可以共享读权限,但是也不能写,此机制提高了读的效率,简而言之,大家一起看看是可以的,但是谁都不能动手(修改)

2.3条件锁

        我也不知道应该怎么命名,所谓“条件锁”是我自己起的名,即用于线程之间同步,A线程下一步动作,依赖于B线程的任务完成后,及时通知A线程的一种机制。

        QT提供了此机制QWaitCondition,此机制结合QMutex与QWaitCondition两个类,让多个线程实现时序运行。

三、QT线程同步应用详解

3.1初级锁

        QMutex是QT封闭的一个互斥量,上锁操作提供两个函数lock与tryLock,前者阻塞方式死等,直到获取到锁的,后者可以设定等待时间,时间到了将返回;解锁操作函数为unlock函数。

        其实,lock与unlock是成对使用的,使用不当将造成严重后果。像new与delete一样,如果new了忘记了delete将造成内存泄漏,如果lock了,忘记了unlock将造成“死锁”,这问题是严重的,而且很难定位,那我们应该怎么规避呢?

        QT提供了一个类QMutexLocker解决此问题,它的策略很简单,用此类实例化一个对象的时候,需要把一个QMutex对象的指针传给它,在构造函数中,它就调用lock给加锁了,然后此对象析构时,在析构函数中会调用unlock,自动释放,如此,将永远都不可能出现死锁的情况了。

QMutex mutex;
void func()
{
	//实例化locker时,构造函数会对mutex加锁
	QMutexLocker locker(&mutex);
	
	//do something
	
	//因为locker是个局部变量,此函数退出时,
	//会析构,自然就会对mutex解锁
}

        这些设计是不是很巧妙?又非常简单。

        好的编程习惯,应该禁止使用直接调用lock、unlock,应该采用以上方法。

3.2读写锁

          读写锁的应用场景,2.2已经介绍,下面看代码实例:

QReadWriteLock lock;
void ReaderThread::run()
{
    lock.lockForRead();
    read_file();
    lock.unlock();
}

void WriterThread::run()
{
    lock.lockForWrite();
    write_file();
    lock.unlock();
}

        如3.1中介绍一样,里面获取锁与释放锁调用调用lock与unlock,将有死锁的风险,这不是严谨的编码风险,同时QT对读写锁与提供了如QMutexLocker一样功能的类,对应读、写锁分别是

QReadLocker、QWriteLocker,所以,以上代码应该改成如下:

QReadWriteLock lock;
void ReaderThread::run()
{
    QReadLocker locker(&lock);
    read_file();
}

void WriterThread::run()
{
    QWriteLocker locker(&lock);
    write_file();
}

        切记,不要写出高风险代码,不想随意相信手册与书本教的东西,实践中的得出的方法,才

是王道、

3.3条件锁

        我们设计这么一种场景,SendThead线程负责往TCP服务端发送数据,RecvThread线程负责从TCP服务端接收数据。

        SendThread发送数据后,需要得到TCP服务端的回应后,再执行下一步操作,所以,此场景下SendThread与RecvThread之间有一个时序关系,即SendThread线程的下一步动作,需要依赖RecvThread线程的结果,代码如下:

青铜写法:

QReadWriteLock lock;
void SendThread()
{
    lock.lock();
	
    SendMsg();
	
	QWaitCondition waitCdn;
	waitCdn.wait(&lock);
	
	
	//do something
	lock.unlock();
}

void RecvThread()
{
    RecvMsg();	
	
    lock.lock();
	QWaitCondition waitCdn;
	waitCdn.wakeAll(&lock);
	lock.unlock();
}

王者写法:

QReadWriteLock lock;
void SendThread()
{
    QMutexLocker locker(&lock);
	
    SendMsg();
	
	QWaitCondition waitCdn;
	waitCdn.wait(&lock);
	
	
	//have recv the Ack, do something	
}

void RecvThread()
{
    RecvMsg();	
	
    QMutexLocker locker(&lock);
	QWaitCondition waitCdn;
	waitCdn.wakeAll(&lock);
}

        QWaitCondition的wait函数,是会对传入的mutex互斥量,进行解锁,然后wait函数自身会阻塞,进到QWaitCondition的wakeAll或者wakeOne被调用,wait函数又马上对互斥量进行加锁。

        下面我们对以上代码进行解析,首先SendThread线程加锁,然后调用发送数据后,启用wait函数,等待RecvThread接收数据,此时wait对互斥量进行解锁,然后自己阻塞。

        下面,我们再看RecvThread,它先进行接收数据,然后,再加锁(此时,此锁被SendThread的wait函数给解了),然后再调用wakeAll函数。触发wait函数加锁,然后返回,SendThread此时又将继续往下执行。

        RecvThread为什么要加锁?因为,此处不加锁,可能导致wakeAll比wait函数先调用的极端情况,导致wait永远无法返回,从而,造成死锁。所以,当使用QWaitCondition编程时,如果没有此机制,此代码是风险代码,是不过关的,可以说,水平是非常次的。

        

你可能感兴趣的:(C++编程,qt,线程同步,读写锁,c++)