Qt同步线程(比较清楚,而且QMutex QMutexLocker QReadWriteLock QSemaphore QWaitCondition 每个都有例子)

Qt同步线程

我们知道,多线程有的时候是很有用的,但是在访问一些公共的资源或者数据时,需要进行同步,否则会使数据遭到破坏或者获取的值不正确。Qt提供了一些类来实现线程的同步,如QMutexQMutexLockerQReadWriteLockQReadLockerQWriteLockerQSemaphoreQWaitCondition。下面我们分别来看它们的用法:

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 != 0, "QMutex::lock", "Overflowin recursion counter");

return;

}

boolisLocked = d->contenders.testAndSetAcquire(0, 1);

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 != 0, "QMutex::lock", "Overflowin recursion counter");

return;

}

//非递归模式时,

boolisLocked = d->contenders.testAndSetAcquire(0, 1);   //尝试加锁

if(!isLocked) {

lockInternal();  //加锁失败则在lockInternal()中一直等到别的线程解锁。

}

}

看看lockInternal的实现

void QMutex::lockInternal()

{

。。。

do {

。。。。//其他代码太复杂,感觉最重要的就是这个while循环了,

//一直循环检测,试图加锁。这我们就好理解,非递归模式的//互斥量,不要在同一个线程里,多次调用lock了。因为第二次调用的时候会在

//这里死循环了

} while(d->contenders != 0 || !d->contenders.testAndSetAcquire(0, 1));

。。。。。。。

}

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 != 0, "QMutex::tryLock", "Overflow in recursion counter");

returntrue;

}

boolisLocked = d->contenders.testAndSetAcquire(0, 1);

if(!isLocked) {

// didn‘tget the lock, wait for it

isLocked = d->wait(timeout);    //尝试加锁失败则等待

if(!isLocked)

returnfalse;

}

d->owner = self;

++d->count;

Q_ASSERT_X(d->count != 0, "QMutex::tryLock", "Overflow in recursion counter");

return true;

}

//尝试加锁失败,(d->contenders.testAndSetAcquire(0,1)返回false,所以继续执行d->wait(timeout);

return (d->contenders.testAndSetAcquire(0, 1) ||d->wait(timeout));

}

//在win下,wait函数实际上是用事件对象实现的

bool QMutexPrivate::wait(inttimeout)

{

if(contenders.fetchAndAddAcquire(1) == 0) {

// lockacquired without waiting

return true;

}

// 当timeout 小于0,则等待时间为INFINITE,这也就是为什么传负数参数时跟lock一样会无限期等待了

boolreturnValue = (WaitForSingleObject(event,timeout < 0 ? 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(0),

"QMutexLocker","QMutex pointer is misaligned");

if (m){

m->lockInline();    // mutex调用lock()加锁

val = reinterpret_cast<quintptr>(m)| quintptr(1u);

} else{

val = 0;

}

}

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 = 0;

switch (flag) {

case 0:

case1:

retVal = moreComplexFunction(flag);

break;

case 2:

{

int status = anotherFunction();

if (status < 0) {

mutex.unlock();

return -2;

}

retVal = status + flag;

}

break;

default:

if (flag > 10) {

mutex.unlock();

return -1;

}

break;

}

mutex.unlock();

return retVal;

}

这样的代码显得很冗余又容易出错。如果我们用QMutexLocker

intcomplexFunction(int flag)

{

QMutexLocker locker(&mutex);

int retVal = 0;

switch (flag) {

case 0:

case 1:

return moreComplexFunction(flag);

case 2:

{

int status = anotherFunction();

if (status < 0)

return -2;

retVal = status + flag;

}

break;

default:

if (flag > 10)

return -1;

break;

}

return retVal;

}

由于locker 是局部变量,在离开函数作用域时,mutex肯定会被解锁。

QreadWriteLock

QreadWriteLock是一个读写锁,主要用来同步保护需要读写的资源。当你想多个读线程可以同时读取资源,但是只能有一个写线程操作资源,而其他线程必须等待写线程完成时,这时候用这个读写锁就很有用了。QreadWriteLock也有递归和非递归模式之分。

我们主要来看看最重要的两个函数是如何实现读写操作的同步的。

void QReadWriteLock::lockForRead ()

该函数lock接了读操作的锁。如果有别的线程已经对lock接了写操作的锁,则函数会阻塞等待。

void QReadWriteLock::lockForRead()

{

QMutexLocker lock(&d->mutex);

Qt::HANDLE self = 0;

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 >0, "QReadWriteLock::lockForRead()",

"Overflowin lock counter");

return;

}

}

// accessCount 小于0说明有写线程在操作资源,则阻塞

while(d->accessCount < 0 || d->waitingWriters) {

++d->waitingReaders;             //自增等待的读线程数

d->readerWait.wait(&d->mutex);

--d->waitingReaders;

}

if(d->recursive)

d->currentReaders.insert(self, 1);

++d->accessCount;    //自增,记录有多少个线程访问了资源

Q_ASSERT_X(d->accessCount > 0, "QReadWriteLock::lockForRead()", "Overflow in lock counter");

}

void QReadWriteLock::lockForWrite ()

该函数给lock加了写操作的锁,如果别的线程已经加了读或者写的锁,则函数会被阻塞。

void QReadWriteLock::lockForWrite()

{

QMutexLocker lock(&d->mutex);

Qt::HANDLE self = 0;

if(d->recursive) {

self = QThread::currentThreadId();

if(d->currentWriter == self) {

--d->accessCount;

Q_ASSERT_X(d->accessCount <0, "QReadWriteLock::lockForWrite()",

"Overflowin lock counter");

return;

}

}

// accessCount不等于0,说明有线程在操作资源,则函数阻塞等待。

// accessCount大于0说明有读线程在读取资源,

// accessCount小于0说明有写线程在写数据

while(d->accessCount != 0) {

++d->waitingWriters;        //自增等待的写线程数

d->writerWait.wait(&d->mutex);

--d->waitingWriters;

}

if(d->recursive)

d->currentWriter = self;

--d->accessCount;

Q_ASSERT_X(d->accessCount < 0, "QReadWriteLock::lockForWrite()", "Overflow in lock counter");

}

void QReadWriteLock::unlock ()

解锁函数,下面我们看看源码是如何实现,让等待的写线程优先于读线程获得互斥量的锁的。

void QReadWriteLock::unlock()

{

QMutexLocker lock(&d->mutex);

Q_ASSERT_X(d->accessCount != 0, "QReadWriteLock::unlock()", "Cannot unlock an unlocked lock");

boolunlocked = false;

if(d->accessCount > 0) {

// 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() <= 0)

d->currentReaders.erase(it);

}

}

// d->accessCount  说明没有线程在操作资源了unlocked为true

unlocked = --d->accessCount == 0;

} else if (d->accessCount < 0 &&++d->accessCount == 0)

{

// d->accessCount <0 说明有写线程在操作。则解锁unlocked = true;

// released awrite lock

unlocked = true;

d->currentWriter = 0;

}

//最重要的就是这里

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();

voidStart();

voidRead();

voidWrite();

voidReadThread1();

voidReadThread2();

voidWriteThread1();

voidWriteThread2();

protected:

private:

string strResource;

QReadWriteLock lock;    //非¤?递ÌY归¨¦的Ì?

};

Lock::Lock()

{

strResource = "Hellworld ......";

}

Lock::~Lock()

{

}

void Lock::Read()

{

cout<<"Readdata :"<<strResource<<endl;

QEventLoop loop;

QTimer::singleShot(2000,&loop,SLOT(quit()));   //为a了¢?使º1得Ì?停ª¡ê留¢?的Ì?时º¡À间?长¡è写¡ä效¡ì果?好?点Ì?,ê?暂Y停ª¡ê2s

loop.exec();

}

void Lock::Write()

{

strResource = "writelock ";

cout<<"Writedata :"<<strResource<<endl;

QEventLoop loop;

QTimer::singleShot(2000,&loop,SLOT(quit()));  //为a了¢?使º1得Ì?停ª¡ê留¢?的Ì?时º¡À间?长¡è写¡ä效¡ì果?好?点Ì?,ê?暂Y停ª¡ê2s

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 >= 0, "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 >= 0, "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(5);

sem.acquire(5);

cout<<"acquire(5);  "<<"remaindresource :"<<sem.available()<<endl;

sem.release(5);

cout<<"release(5)  "<<"remaindresource :"<<sem.available()<<endl;

sem.release(10);

cout<<"release(10)  "<<"remaindresource :"<<sem.available()<<endl;

sem.acquire(15);

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

时间: 2024-08-28 10:42:45

Qt同步线程(比较清楚,而且QMutex QMutexLocker QReadWriteLock QSemaphore QWaitCondition 每个都有例子)的相关文章

Qt同步线程(QMutex QMutexLocker QReadWriteLock QSemaphore QWaitCondition )

Qt同步线程 我们知道,多线程有的时候是很有用的,但是在访问一些公共的资源或者数据时,需要进行同步,否则会使数据遭到破坏或者获取的值不正确.Qt提供了一些类来实现线程的同步,如QMutex,QMutexLocker,QReadWriteLock,QReadLocker,QWriteLocker,QSemaphore和QWaitCondition.下面我们分别来看它们的用法: QMutex 首先,简单的了解一下QMutex提供的函数. 构造函数:QMutex ( RecursionMode mod

Qt 的线程与事件循环——可打印threadid进行观察槽函数到底是在哪个线程里执行,学习moveToThread的使用)

周末天冷,索性把电脑抱到床上上网,这几天看了 dbzhang800 博客关于 Qt 事件循环的几篇 Blog,发现自己对 Qt 的事件循环有不少误解.从来只看到现象,这次借 dbzhang800 的博客,就代码论事,因此了解到一些 Qt 深层的实现,虽然是在 Qt 庞大的构架里只算的是冰山的一角,确让人颇为收益. 从 dbzhang800 的博客中转载两篇关于事件循环的文章,放在一起,写作备忘. 再次提到的一点是:事件循环和线程没有必然关系. QThread 的 run() 方法始终是在一个单独

Qt经典—线程、事件与Qobject

(转自:http://www.cnblogs.com/newstart/archive/2013/07/20/3202118.html) 先决条件 考虑到本文并不是一个线程编程的泛泛介绍,我们希望你有如下相关知识: C++基础: Qt 基础:QOjbects , 信号/槽,事件处理: 了解什么是线程.线程与进程间的关系和操作系统: 了解主流操作系统如何启动.停止.等待并结束一个线程: 了解如何使用mutexes, semaphores 和以及wait conditions 来创建一个线程安全/可

qt中线程的使用方法

QT中使用线程可以提高工作效率. 要使用线程要经过一下四个步骤: (1)先创建一个c++ class文件,记得继承Thread,创建步骤如下: a.第一步 b.第二步 (2)自定义一个run函数,以后启动线程的时候,程序就会跳转到run函数中 void run(); (3)初始化线程 HDThread mythread = new HDThread(); (4)启动线程 mythread->start(); 下面来看看线程使用的具体列子: 线程头文件hdthread.h: 1 #ifndef H

线程同步--线程间通信

一.线程同步 线程的同步方法跟其他系统下类似,我们可以用原子操作,可以用 mutex,lock 等. iOS 的原子操作函数是以 OSAtomic 开头的,比如:OSAtomicAdd32, OSAtomicOr32 等等.这些函数可以直接使用,因为它 们是原子操作. iOS 中的 mutex 对应的是 NSLock,它遵循 NSLooking 协议,我们可以使用 lock, tryLock, lockBeforeData:来加锁,用 unLock 来解锁.使用示例: BOOL moreToDo

QT子线程与主线程的信号槽通信

最近用QT做一个服务器,众所周知,QT的主线程必须保持畅通,才能刷新UI.所以,网络通信端采用新开线程的方式.在涉及到使用子线程更新Ui上的控件时遇到了点儿麻烦.网上提供了很多同一线程不同类间采用信号槽通信的方式,但是并不完全适合线程间的信号槽通信,这主要体现在自定义消息的传递上. 首先我们看看一般的方式: testthread.h 文件 #ifndef TESTTHREAD_H #define TESTTHREAD_H #include <QThread> #include "ms

深入理解计算机系统——第12章:用信号量同步线程

用信号量同步线程: 同步错误: 一般而言你没有办法预测操作系统是否将你的线程选择一个正确的顺序执行. 12.5.1 进度图 (1)进度图:将n个并发线程的执行模型化为一条n维笛卡尔空间中的轨迹线. (2)每条轴k对应着线程k的进度. (3)每个点Ik代表着k线程已完成指令Ik这一个状态,原点处代表初始状态. (4)进度图是指令执行模型化为从一个状态到另一个状态的转换,两条指令不能在同一时刻完成,对角线不允许的.

用GCD线程组与GCD信号量将异步线程转换为同步线程

有时候我们会碰到这样子的一种情形: 同时获取两个网络请求的数据,但是网络请求是异步的,我们需要获取到两个网络请求的数据之后才能够进行下一步的操作,这个时候,就是线程组与信号量的用武之地了. 线程组用以监听线程的执行情况,而信号量就是用来将异步线程转化为同步线程. 以下是打印的数据: 2015-02-25 18:34:23.208 YXMWeather[265:8748] 请求1数据 2015-02-25 18:34:23.209 YXMWeather[265:8790] 1信号量结束 2015-

38.线程三--&gt;多线程数据安全和同步线程的方法

学习要点 多线程数据安全 同步线程的方法 class MyThread implements Runnable{//MyThread 实现Runnable接口 int i = 100; public void run(){ //复写run方法 while(true){ synchronized(this){  //线程同步代码 //Thread.currentThread()获取当前这段代码正在哪个线程运行 System.out.println(Thread.currentThread().ge