QT多線程(三)線程互斥與同步

前言


線程之間存在着相互制約的關係:

  1. 互斥關係,如線程爭奪I/O設備而導致一方必須等待一方使用結束後方可使用

  2. 同步關係,完成同一任務的線程之間,需要協調它們的工作而相互等待、交互

 

臨界區


先看這個類:

class Key
{
public:
	Key() { key = 0 ; }
	int creatKey() { ++key; return key; }
	int value() const { return key ; }

private:
	int key;
};

在多線程環境下,這個類是不安全的,存在多個線程同時修改成員key的情況,其結果是不可預知的,雖然++k只是一條語句,但它並不是原子操作,編譯後將會展開成三條指令:將變量載入寄存器、將 寄存器中的值加1、將寄存器中的值寫回主存。

爲了保證類Key 在多線程環境下正確執行,上述三條機器指令必須串行執行且不允許被打斷,並且每次只有一個線程操作。

一次只允許一個線程使用的資源成爲臨界資源,臨界資源可以是一塊內存、一個數據結構、一個文件等,必須互斥執行的代碼段被稱爲“臨界區”。

QT爲實現線程之間的互斥與同步提供了以下幾個類:QMutex、QMutexLocker、QReadWriteLocker、QReadLocker、QWriteLocker、QSemaphore、QWaitCondition。

 

 

QMutex / QMutexLocker


QMutex可以保護臨界區一次只被一個線程執行。如果一個線程正在使用臨界資源,其它線程則會被阻塞,直到臨界資源被釋放。

使用QMtuex操作Key類:

class Key
{
public:
    Key() { key = 0 ; }
    int creatKey() 
    { mutex.lock();  ++key; return key; mutex.unlock(); }
        
    int value() const 
    { mutex.lock();  return key ;  mutex.unlock(); }
​
private:
    int key;
    QMutex mutex;
};

雖然是用了mutex進行了互斥操作,但unlock在return語句之後,導致unlock()無法執行。

QMutex還提供了try()函數,如果互斥量已經被鎖定,就立即返回,不會等待解鎖。

 

QMutexLocker可以解決此問題:

class Key
{
public:
    Key() { key = 0 ; }
    int creatKey() 
    { QMutexLocker locker(&mutex);  ++key; return key; }
        
    int value() const 
    { QMutexLocker locker(&mutex);  return key ; }
​
private:
    int key;
    QMutex mutex;
};

locker作爲局部變量,在退出作用域後會自動調用析構函數來unlock對互斥量mutex解鎖。

 

QSemaphore


Qsemaphore是信號量,可以理解爲對互斥量的功能擴展,互斥量只能鎖定一次釋放一次,而信號量可以釋放多次。acquire(n=1)函數可以獲取n個資源,當沒有足夠的資源時,線程會被阻塞。直到有n個資源可被利用。

可以將acquire(n=1)理解爲將 -n, release(n=1)理解爲 +n。

tryAcqire(n)函數在沒有n個資源的情況下會立即返回。

下面這個例子,freed表示有多少個單元可以寫入數據,初始化全部可寫。used表示當前有多少個單元可以讀入數據,初始化爲0個。

producer線程和customer線程同時運行,但開始時,customer線程裏的used.acquire()想要獲得一個可讀單元,而目前沒有可讀單元,所以customer線程會被阻塞,直到produce線程釋放資源。

#include <QtCore/QCoreApplication>
#include <QSemaphore>
#include <QThread>
#include <iostream>
​
const int DataSize = 100;
const int BufferSize = 10;
char buffer[BufferSize];
​
QSemaphore freed(BufferSize); //有BufferSize個可寫單元
QSemaphore used(0); //有0個可讀單元
​
class Producer : public QThread
{
protected:
    void run()
    {
        for (int i = 0; i < DataSize; ++i)
        {
            freed.acquire(); //freed -= 1 用掉一個可寫單元
            std::cout<<"P";
            used.release(); //used += 1 釋放一個可讀單元
        }
    }
};
​
class Consumer : public QThread
{
protected:
    void run()
    {
        sleep(1); 
        for (int i = 0; i < DataSize; ++i)
        {
            used.acquire(); //用掉一個可讀單元
            std::cout<<"C";
            freed.release(); //釋放一個可寫單元
        }
        std::cerr<<std::endl;
    }
};
​
int main(int argc, char *argv[])
{
    Producer producer;
    Consumer consumer;
    producer.start();
    consumer.start();
    producer.wait();
    consumer.wait();
    system("pause");
    return 0;
}

 

 

QwaitCondition


 

QwaitCondition類有兩個主要的函數,wait() 和 wakeAll()。

wait()函數有兩個參數,第一個爲互斥量,第二個爲等待時間,默認無限長。

調用wait()的線程使互斥量解鎖,使自己被阻塞直到其它線程調用wakeAll()或者wakeOne() (返回true),或者超過等待時間(返回false)。重新將互斥量置爲鎖定狀態。

#include <QtCore>
#include <stdio.h>
#include <stdlib.h>
​
const int DataSize = 10000;
const int BufferSize = 8192;
int buffer[BufferSize];
​
QWaitCondition bufferEmpty; //緩衝區有空位條件
QWaitCondition bufferFull;   //緩衝區有可用數據
QMutex mutex;
int numUsedBytes = 0;  //可用字節
int rIndex=0;          //當前讀取緩衝區位置
​
class Producer : public QThread
{
public:
    void run()
    {
        for (int i = 0; i < DataSize; ++i)
        {
            mutex.lock();
            if (numUsedBytes == BufferSize) //如果已經寫滿
                bufferEmpty.wait(&mutex); //解鎖並等待緩衝區有空位條件bufferEmpty,一旦等到了,重新上鎖
            //         mutex.unlock();
            buffer[i % BufferSize] = numUsedBytes; //寫數據
            //         mutex.lock();
            ++numUsedBytes; //可用字節+1
            bufferFull.wakeAll(); //喚醒緩衝區有可用數據條件
            mutex.unlock(); //
        }
    }
};
​
​
class Consumer : public QThread
{
public:
    void run()
    {
        forever 
        {
            mutex.lock();
            if (numUsedBytes == 0) //如果沒有可用數據
                bufferFull.wait(&mutex); //解鎖等待有可用數據條件bufferFull,等到了,重新上鎖
            //         mutex.unlock();
            printf("%ul::[%d]=%d->%d \n", currentThreadId (),rIndex,buffer[rIndex],numUsedBytes);
            //         mutex.lock();
            rIndex = (++rIndex)%BufferSize;
            --numUsedBytes; //用掉一個字節
            bufferEmpty.wakeAll(); //喚醒有空位條件
            mutex.unlock();
        }
        printf("\n");
    }
};
​
​
int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
​
    for (int i = 0; i < BufferSize; ++i) 
            buffer[i] = -1;             
    
    Producer producer;
    Consumer consumerA;
    Consumer consumerB;
​
    producer.start();
    consumerA.start();
    consumerB.start();
    producer.wait();
    consumerA.wait();
    consumerB.wait();
    return 0;
}

 

 

QReadWriteLock


Qt中的QReadWriteLock類爲我們提供了讀寫鎖的功能。讀寫鎖是用來保護可以被讀訪問和寫訪問的資源的一種同步工具。

可以讓多個線程同時的對資源進行讀訪問,但只要有一個線程要對資源進行寫訪問時,所有其他的線程必須等待,直到寫訪問完成。對於這種情況,讀寫鎖是非常有用的。

#include <QtCore/QCoreApplication>
#include <QReadWriteLock>
#include <QThread>
#include <iostream>
​
QReadWriteLock rwLock;  
​
class Write : public QThread
{
public:
    void run()
    {
        forever
        {
            rwLock.lockForWrite();
            std::cout << "write" << std::endl;
            sleep(1);
            rwLock.unlock();
        }
    }
};
​
class Read : public QThread
{
    void run()
    {
        forever
        {
            rwLock.lockForRead();
            std::cout << "read" << std::endl;
            rwLock.unlock();
        }
    }
};
int main(int argc, char *argv[])
{
    Write write;
    Read read[10];
    
    write.start();
    for(int i = 0; i < 10; i++) 
        read[i].start();
​
    write.wait();
    for(int i = 0; i < 10; i++) 
        read[i].wait();
    return 0;
}

 

QT還提供了QReadLocker和QWriteLocker兩個類,這兩個類在構造函數中接收一個QReadWriteLock,構造函數中對其鎖定,析構函數進行解鎖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章