前言
線程之間存在着相互制約的關係:
-
互斥關係,如線程爭奪I/O設備而導致一方必須等待一方使用結束後方可使用
-
同步關係,完成同一任務的線程之間,需要協調它們的工作而相互等待、交互
臨界區
先看這個類:
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,構造函數中對其鎖定,析構函數進行解鎖。