官术网_书友最值得收藏!

1.3 線程同步

線程同步即當有一個線程在對內存地址進行操作時,其他線程都不可以對這個內存地址進行操作,直到該線程完成操作,其他處于等待的線程才能對該內存地址進行操作,而別的線程又處于等待狀態。

1.3.1 互斥量

線程鎖能夠保證臨界資源的安全性,通常,每個臨界資源需要一個線程鎖進行保護。下面介紹兩個概念。

臨界資源:每次只允許一個線程訪問的資源。

線程間互斥:多個線程在同一時刻都需要訪問臨界資源。

QMutex、QReadWriteLock、QSemaphore和QWaitCondition可提供線程同步的手段。使用線程主要是希望它們可以盡可能并發執行,而在一些關鍵點上線程之間需要停止或等待。例如,如果兩個線程要同時訪問同一個全局變量,則無法實現。

QMutex 類提供相互排斥的鎖,或稱為互斥量。在一個時刻至多一個線程擁有QMutex的某對象m_Mutex。假如一個線程試圖訪問已經被鎖定的m_Mutex,那么該線程將休眠,直到擁有m_Mutex對象的線程對此m_Mutex解鎖。QMutex常用來保護共享數據訪問。QMutex類的所有成員函數是線程安全的。

在程序中使用QMutex 時需要聲明頭文件,在程序開始之前聲明QMutex m_Mutex,在只能一個線程訪問的代碼之前加鎖,代碼之后解鎖。相關的代碼如下。

頭文件聲明: #include <QMutex>

互斥量聲明: QMutex m_Mutex;

互斥量加鎖: m_Mutex.lock();

互斥量解鎖: m_Mutex.unlock();

如果對沒有加鎖的互斥量進行解鎖,那么執行的結果是可能造成死鎖。互斥量的加鎖(Lock)和解鎖(Unlock)必須在同一線程中成對出現。

QMutex有兩種模式:Recursive和NonRecursive。

Recursive:一個線程可以對mutex對象多次加鎖,直到相應次數的解鎖調用后,mutex對象才真正被解鎖。

NonRecursive:默認模式,mutex對象只能被加鎖一次。

如果使用了m_Mutex.lock加鎖而沒有使用對應的m_Mutex.unlock解鎖,就會造成死鎖,其他線程將永遠也得不到接觸m_Mutex鎖住的共享資源的機會。盡管可以不使用lock而使用tryLock(timeout)來避免“死等”造成的死鎖[tryLock(負值)==lock()],但是還是可能造成錯誤。兩個函數的具體情況如下。

bool tryLock:如果當前其他線程已對QMutex對象加鎖,則該調用會立即返回,而不被阻塞。

bool tryLock(int timeout):如果當前其他線程已對該QMutex對象加鎖,則該調用會等待一段時間,直到超時。

下面通過一個多線程售票的例子來看一下QMutex的使用。在這個例子中,首先通過繼承QObject類,創建TicketSeller類,并創建兩個對象——seller1和seller2,然后通過創建線程t1和t2,再將對象交給線程。在具體售票過程中,售票前先對互斥對象票的總數加鎖,售票后再解鎖釋放。

#include <QCoreApplication>
#include <QObject>
#include <QThread>
#include <QMutex>
#include <string>
#include <iostream>
class TicketSeller : public QObject
{
public:
TicketSeller();
~TicketSeller();
public slots:
void sale();
public:
int* tickets;
QMutex* mutex;
std::string name;
};
TicketSeller::TicketSeller()
{
tickets = 0;
mutex = NULL;
}
TicketSeller::~TicketSeller()
{
}
void TicketSeller::sale()
{
while((*tickets) > 0)
{
mutex->lock();//加鎖
std::cout << name << " : " << (*tickets)-- << std::endl;
mutex->unlock();//解鎖
}
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int ticket = 100;
QMutex mutex;
/*創建、設置線程1*/
//創建線程1
QThread t1;
TicketSeller seller1;
//設置線程1
seller1.tickets = &ticket;
seller1.mutex = &mutex;
seller1.name = "seller1";
//將對象移動到線程
seller1.moveToThread(&t1);
/*創建、設置線程2*/
//創建線程2
QThread t2;
TicketSeller seller2;
seller2.tickets = &ticket;
seller2.mutex = &mutex;
seller2.name = "seller2";
//將對象移動到線程
seller2.moveToThread(&t2);
QObject::connect(&t1, &QThread::started, &seller1, &TicketSeller::sale);
QObject::connect(&t2, &QThread::started, &seller2, &TicketSeller::sale);
t1.start();
t2.start();
return a.exec();
}

編譯運行后,可以看到票的總數通過兩個線程從100依次遞減到0。

另外還有一個QMutexLocker 類,主要用來管理 QMutex。使用 QMutexLocker 的好處是可以防止線程死鎖。QMutexLocker在構造的時候加鎖,析構的時候解鎖。

1.3.2 死鎖以及解決方案

多線程以及多進程可改善系統資源的利用率,并提高系統的處理能力。然而,運行過程中因爭奪資源可能會造成一種僵局(Deadly-embrace),若無外力作用,這些進程(線程)都將無法向前推進。

產生死鎖的條件,一是系統中存在多個臨界資源且臨界資源不可搶占,二是線程需要多個臨界資源才能繼續執行。死鎖可采用的解決方案如下:對使用的每個臨界資源都分配一個唯一的序號,對每個臨界資源對應的線程鎖分配相應的序號,系統中的每個線程按照嚴格遞增的次序請求臨界資源。

1.3.3 讀寫鎖

QReadWriteLock 與QMutex相似,但對讀寫操作區別對待,可以允許多個讀者同時讀數據,但只能有一個寫,并且讀寫操作不能同時進行。使用QReadWriteLock而不是QMutex,可以使多線程程序更具有并發性。 QReadWriteLock的默認模式是NonRecursive。

QReadWriteLock類成員函數如下。

QReadWriteLock:讀寫鎖構造函數。

QReadWriteLock ( RecursionMode recursionMode):遞歸模式。在這種模式下,一個線程可以加多次相同的讀寫鎖,直到相應數量的unlock被調用才能被解鎖。

void lockForRead:加讀鎖。

void lockForWrite:加寫鎖。

QReadWriteLock ( RecursionMode NonRecursive):非遞歸模式。在這種模式下,一個線程僅可以加讀寫鎖一次,不可遞歸。

bool tryLockForRead:嘗試讀鎖定。如果讀鎖定成功,則返回true,否則它立即返回false。

bool tryLockForRead ( int timeout ):嘗試讀鎖定。如果讀鎖定成功,則返回true;如果不成功,則等待timeout時間,等待其他線程解鎖,當timeout為負數時,一直等待。

bool tryLockForWrite:嘗試寫鎖定。如果寫鎖定成功,則返回true,否則它立即返回false。

bool tryLockForWrite ( int timeout ):嘗試寫鎖定。如果寫鎖定成功,則返回true;如果不成功,則等待timeout時間,等待其他線程解鎖,當timeout為負數時,一直等待。

void unlock:解鎖。

下面給出了一個QReadWriteLock的使用實例,代碼如下。

QReadWriteLock lock;
void ReaderThread::run()
{
lock.lockForRead();
read_file();
lock.unlock();
}
void WriterThread::run()
{
lock.lockForWrite();
write_file();
lock.unlock();
}

1.3.4 條件變量

QWaitCondition條件變量允許一個線程通知其他線程,如果所等待的某個條件已經滿足,可以繼續運行。一個或多個線程可以在同一個條件變量上等待。當條件滿足時,可以調用wakeOne從所有等待在該條件變量上的線程中隨機喚醒一個線程繼續運行,也可以使用wakeAll同時喚醒所有等待在該條件變量上的線程。

QWaitCondition和QSemaphore一樣,因為要訪問共享資源,所以要和QMutex配合使用。

主站蜘蛛池模板: 合水县| 仁布县| 夹江县| 甘德县| 兰西县| 湘潭县| 牟定县| 宁蒗| 偏关县| 绍兴市| 钟山县| 梅州市| 灌阳县| 永兴县| 宣城市| 车险| 垫江县| 宁晋县| 积石山| 宝坻区| 纳雍县| 砀山县| 松桃| 上杭县| 曲沃县| 闸北区| 遂宁市| 剑阁县| 肃宁县| 柯坪县| 普宁市| 鹤岗市| 卢龙县| 泾源县| 永清县| 登封市| 神木县| 洞头县| 五莲县| 武威市| 桦南县|