zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    CodeForces 659F Polycarp and Hay
    CodeForces 713C Sonya and Problem Wihtout a Legend
    CodeForces 712D Memory and Scores
    CodeForces 689E Mike and Geometry Problem
    CodeForces 675D Tree Construction
    CodeForces 671A Recycling Bottles
    CodeForces 667C Reberland Linguistics
    CodeForces 672D Robin Hood
    CodeForces 675E Trains and Statistic
    CodeForces 676D Theseus and labyrinth
  • 原文地址:https://www.cnblogs.com/findumars/p/5176046.html
Copyright © 2011-2022 走看看