轉自princetengC++多線程——讀寫鎖shared_lock/shared_mutex
何為讀寫鎖
相比互斥鎖,讀寫鎖允許更高的並行性,互斥量要么鎖住狀態要么不加鎖,而且一次只有一個線程可以加鎖。
讀寫鎖可以有三種狀態:
- 讀模式加鎖狀態;
- 寫模式加鎖狀態;
- 不加鎖狀態;
只有一個線程可以占有寫模式的讀寫鎖,但是可以有多個線程占有讀模式的讀寫鎖。讀寫鎖也叫做“共享-獨占鎖”,當讀寫鎖以讀模式鎖住時,它是以共享模式鎖住的;當它以寫模式鎖住時,它是以獨占模式鎖住的。
- 當讀寫鎖處於寫加鎖狀態時,在其解鎖之前,所有嘗試對其加鎖的線程都會被阻塞;
- 當讀寫鎖處於讀加鎖狀態時,所有試圖以讀模式對其加鎖的線程都可以得到訪問權,但是如果想以寫模式對其加鎖,線程將阻塞。這樣也有問題,如果讀者很多,那么寫者將會長時間等待,如果有線程嘗試以寫模式加鎖,那么后續的讀線程將會被阻塞,這樣可以避免鎖長期被讀者占有。
shared_mutex
C++17起。
shared_mutex 類是一個同步原語,可用於保護共享數據不被多個線程同時訪問。與便於獨占訪問的其他互斥類型不同,shared_mutex 擁有二個訪問級別:共享 - 多個線程能共享同一互斥的所有權;獨占性 - 僅一個線程能占有互斥。
- 若一個線程已經通過lock或try_lock獲取獨占鎖(寫鎖),則無其他線程能獲取該鎖(包括共享的)。嘗試獲得讀鎖的線程也會被阻塞。
- 僅當任何線程均未獲取獨占性鎖時,共享鎖(讀鎖)才能被多個線程獲取(通過 lock_shared 、try_lock_shared )。
- 在一個線程內,同一時刻只能獲取一個鎖(共享或獨占性)。
成員函數主要包含兩大類:排他性鎖定(寫鎖)和共享鎖定(讀鎖)。
排他性鎖定
lock鎖定互斥。若另一線程已鎖定互斥,則lock的調用線程將阻塞執行,直至獲得鎖。若已以任何模式(共享或排他性)占有 mutex 的線程調用 lock ,則行為未定義。也就是說,已經獲得讀模式鎖或者寫模式鎖的線程再次調用lock的話,行為是未定義的。注意:通常不直接使用std::shared_mutex::lock(),而是通過unique_lock或者lock_guard進行管理。
unlock解鎖互斥。互斥必須為當前執行線程所鎖定,否則行為未定義。如果當前線程不擁有該互斥還去調用unlock,那么就不知道去unlock誰,行為是未定義的。注意:通常不直接調用 unlock() 而是用 std::unique_lock 與 std::lock_guard 管理排他性鎖定。
共享鎖定
std::shared_mutex::lock_shared。相比mutex,shared_mutex還擁有lock_shared函數。該函數獲得互斥的共享所有權。若另一線程以排他性所有權保有互斥,則lock_shared的調用者將阻塞執行,直到能取得共享所有權。若已以任何模式(排他性或共享)占有 mutex 的線程調用 lock_shared ,則行為未定義。即:當以讀模式或者寫模式擁有鎖的線程再次調用lock_shared時,行為是未定義的,可能產生死鎖。若多於實現定義最大數量的共享所有者已以共享模式鎖定互斥,則 lock_shared 阻塞執行,直至共享所有者的數量減少。所有者的最大數量保證至少為 10000。注意:通常不直接調用 lock_shared() 而是用 std::shared_lock 管理共享鎖定。shared_lock與unique_lock的使用方法類似。
std::shared_mutex::unlock_shared。將互斥從調用方線程的共享所有權釋放。當前執行線程必須以共享模式鎖定互斥,否則行為未定義。通常不直接調用 unlock_shared() 而是用 std::shared_lock 管理共享鎖定。
shared_lock
C++14起。
類 shared_lock 是通用共享互斥所有權包裝器(unique_lock則是獨占互斥所有權包裝器),允許延遲鎖定、定時鎖定和鎖所有權的轉移。鎖定 shared_lock,會以共享模式鎖定關聯的共享互斥(std::unique_lock 可用於以排他性模式鎖定)。
- std::shared_lock<Mutex>::lock以共享模式鎖定關聯互斥。等效於調用 mutex()->lock_shared();
- std::shared_lock<Mutex>::try_lock嘗試以共享模式鎖定關聯互斥而不阻塞。等效於調用 mutex()->try_lock_shared()。若無關聯互斥,或互斥已被鎖定,則拋出 std::system_error 。
- std::shared_lock<Mutex>::unlock從共享模式解鎖關聯互斥。等效於調用 mutex()->unlock_shared()。
示例
1 #include <iostream> 2 #include <mutex> //unique_lock 3 #include <shared_mutex> //shared_mutex shared_lock 4 #include <thread> 5 6 std::mutex mtx; 7 8 class ThreadSaferCounter 9 { 10 private: 11 mutable std::shared_mutex mutex_; 12 unsigned int value_ = 0; 13 public: 14 ThreadSaferCounter(/* args */) {}; 15 ~ThreadSaferCounter() {}; 16 17 unsigned int get() const { 18 //讀者, 獲取共享鎖, 使用shared_lock 19 std::shared_lock<std::shared_mutex> lck(mutex_);//執行mutex_.lock_shared(); 20 return value_; //lck 析構, 執行mutex_.unlock_shared(); 21 } 22 23 unsigned int increment() { 24 //寫者, 獲取獨占鎖, 使用unique_lock 25 std::unique_lock<std::shared_mutex> lck(mutex_);//執行mutex_.lock(); 26 value_++; //lck 析構, 執行mutex_.unlock(); 27 return value_; 28 } 29 30 void reset() { 31 //寫者, 獲取獨占鎖, 使用unique_lock 32 std::unique_lock<std::shared_mutex> lck(mutex_);//執行mutex_.lock(); 33 value_ = 0; //lck 析構, 執行mutex_.unlock(); 34 } 35 }; 36 ThreadSaferCounter counter; 37 void reader(int id){ 38 while (true) 39 { 40 std::this_thread::sleep_for(std::chrono::seconds(1)); 41 std::unique_lock<std::mutex> ulck(mtx);//cout也需要鎖去保護, 否則輸出亂序 42 std::cout << "reader #" << id << " get value " << counter.get() << "\n"; 43 } 44 } 45 46 void writer(int id){ 47 while (true) 48 { 49 std::this_thread::sleep_for(std::chrono::seconds(1)); 50 std::unique_lock<std::mutex> ulck(mtx);//cout也需要鎖去保護, 否則輸出亂序 51 std::cout << "writer #" << id << " write value " << counter.increment() << "\n"; 52 } 53 } 54 55 int main() 56 { 57 std::thread rth[10]; 58 std::thread wth[10]; 59 for(int i=0; i<10; i++){ 60 rth[i] = std::thread(reader, i+1); 61 } 62 for(int i=0; i<10; i++){ 63 wth[i] = std::thread(writer, i+1); 64 } 65 66 for(int i=0; i<10; i++){ 67 rth[i].join(); 68 } 69 for(int i=0; i<10; i++){ 70 wth[i].join(); 71 } 72 return 0; 73 }