- 統信UOS應用開發進階教程
- 統信軟件技術有限公司
- 1966字
- 2022-06-17 10:38:49
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配合使用。
- Containerization with LXC
- 發布!設計與部署穩定的分布式系統(第2版)
- UNIX操作系統設計
- 白話區塊鏈
- Ubuntu Linux操作系統
- 構建可擴展分布式系統:方法與實踐
- 高性能Linux服務器構建實戰:運維監控、性能調優與集群應用
- 深入Linux內核架構與底層原理(第2版)
- Moodle 3.x Teaching Techniques(Third Edition)
- 注冊表應用完全DIY
- 計算機系統:基于x86+Linux平臺
- 寫給架構師的Linux實踐:設計并實現基于Linux的IT解決方案
- Introduction to R for Quantitative Finance
- Learn SwiftUI
- Android應用性能優化最佳實踐