• 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;
    
      }
    
      bool isLocked = 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;
    
    }
    
    //非递归模式时,
    
      bool isLocked = 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");
    
            return true;
    
        }
    
        boolisLocked = d->contenders.testAndSetAcquire(0, 1);
    
        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 != 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();
    
    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(2000,&loop,SLOT(quit()));   
    
    loop.exec();
    
    }
    
    void Lock::Write()
    
    {
    
    strResource = "writelock ";
    
    cout<<"Writedata :"<<strResource<<endl;
    
    QEventLoop loop;
    
    QTimer::singleShot(2000,&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 >= 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

  • 相关阅读:
    成为java程序员的学习过程
    解决自动添加局域网内打印机的问题
    通过主机标头实现多个SharePoint Web应用程序共用一个端口
    MSDN教学短片WPF 3(WPF的图形透明效果)
    MSDN 教学短片 WPF 14(2D动画之—Trigger)
    MSDN 教程短片 WPF 8(WPF样式与资源)
    MSDN 教学短片WPF 5(Linear/RadialGradientBrush)
    MSDN 教学短片 WPF 12(画布)
    MSDN 教学短片WPF 4(笔刷)
    MSDN 教程短片 WPF 17(简单播放器的制作)
  • 原文地址:https://www.cnblogs.com/xiangtingshen/p/11267523.html
Copyright © 2020-2023  润新知