在多執行緒開發中,經常會遇到資料同步,很多情況下用鎖都是一個很好的選擇。C++中常用的鎖主要有下面幾種:
互斥鎖(std::mutex
)
- 這是最基本的一種鎖。它用於保護共享資源,在任意時刻,最多隻有一個執行緒可以獲取該鎖,從而訪問被保護的資源。當一個執行緒獲取了互斥鎖後,其他試圖獲取該鎖的執行緒會被阻塞,直到持有鎖的執行緒釋放它。
- 例如,在一個多執行緒程式中,如果多個執行緒需要訪問和修改同一個全域性變數,就可以使用互斥鎖來確保在同一時間只有一個執行緒能夠進行修改操作,避免資料競爭導致的錯誤結果。
1 #include <iostream> 2 #include <mutex> 3 #include <thread> 4 5 std::mutex m; 6 int counter = 0; 7 8 void increment() { 9 m.lock(); 10 counter++; 11 std::cout << "Counter value in thread " << std::this_thread::get_id() << " is " << counter << std::endl; 12 m.unlock(); 13 } 14 15 int main() { 16 std::thread t1(increment); 17 std::thread t2(increment); 18 t1.join(); 19 t2.join(); 20 return 0; 21 }
遞迴互斥鎖(std::recursive_mutex
)
- 遞迴互斥鎖允許同一個執行緒多次獲取該鎖。它內部會記錄鎖的獲取次數,每獲取一次,計數加 1,每次釋放鎖時,計數減 1,當計數為 0 時,鎖才真正被釋放,可供其他執行緒獲取。
- 假設在一個複雜的函式呼叫鏈中,函式 A 呼叫函式 B,函式 B 又呼叫函式 A,並且這些函式都需要訪問同一個受保護的資源。如果使用普通互斥鎖,就會出現死鎖,而遞迴互斥鎖就可以避免這種情況,因為它允許同一執行緒多次獲取鎖。
1 #include <iostream> 2 #include <mutex> 3 #include <thread> 4 5 std::recursive_mutex rm; 6 7 void recursiveFunction(int count) { 8 rm.lock(); 9 if (count > 0) { 10 std::cout << "Recursive call with count = " << count << std::endl; 11 recursiveFunction(count - 1); 12 } 13 rm.unlock(); 14 } 15 16 int main() { 17 std::thread t(recursiveFunction, 3); 18 t.join(); 19 return 0; 20 }
讀寫鎖(std::shared_mutex
) C++17開始才有
- 讀寫鎖主要用於區分對共享資源的讀操作和寫操作。它有兩種獲取模式:共享模式(讀模式)和獨佔模式(寫模式)。
- 多個執行緒可以同時以共享模式獲取讀寫鎖,這意味著它們可以同時讀取共享資源,而不會相互干擾。但是,當一個執行緒要以獨佔模式獲取讀寫鎖(進行寫操作)時,其他執行緒(無論是讀操作還是寫操作)都不能獲取該鎖,直到寫操作完成並釋放鎖。這種機制在有大量讀操作和少量寫操作的場景下,可以提高程式的併發效能。例如,在一個快取系統中,多個執行緒可能經常讀取快取中的資料,只有在快取資料需要更新時才會進行寫操作,使用讀寫鎖可以很好地處理這種情況。
1 #include <iostream> 2 #include <shared_mutex> 3 #include <thread> 4 #include <vector> 5 6 std::shared_mutex smtx; 7 int shared_data = 0; 8 9 void read_data() { 10 std::shared_lock<std::shared_mutex> lock(smtx); 11 std::cout << "Read data: " << shared_data << std::endl; 12 } 13 14 void write_data(int new_value) { 15 std::unique_lock<std::shared_mutex> lock(smtx); 16 shared_data = new_value; 17 std::cout << "Wrote data: " << shared_data << std::endl; 18 } 19 20 int main() { 21 std::vector<std::thread> read_threads; 22 for (int i = 0; i < 5; i++) { 23 read_threads.push_back(std::thread(read_data)); 24 } 25 std::thread write_thread(write_data, 10); 26 for (auto& t : read_threads) { 27 t.join(); 28 } 29 write_thread.join(); 30 return 0; 31 }
定時互斥鎖(std::timed_mutex
)
- 定時互斥鎖是
std::mutex
的擴充套件。除了具備std::mutex
的基本功能外,它還允許執行緒在嘗試獲取鎖時設定一個超時時間。 - 如果在規定的超時時間內無法獲取鎖,執行緒不會一直等待,而是可以執行其他操作或者返回錯誤資訊。這在一些對時間敏感的場景中非常有用,比如在一個實時系統中,執行緒不能因為等待一個鎖而無限期地阻塞,需要在一定時間後放棄獲取並進行其他處理。
1 #include <iostream> 2 #include <chrono> 3 #include <thread> 4 #include <mutex> 5 6 std::timed_mutex tm; 7 8 void tryLockFunction() { 9 if (tm.try_lock_for(std::chrono::seconds(1))) { 10 std::cout << "Acquired lock" << std::endl; 11 std::this_thread::sleep_for(std::chrono::seconds(2)); 12 tm.unlock(); 13 } else { 14 std::cout << "Could not acquire lock in time" << std::endl; 15 } 16 } 17 18 int main() { 19 std::thread t1(tryLockFunction); 20 std::thread t2(tryLockFunction); 21 t1.join(); 22 t2.join(); 23 return 0; 24 }
遞迴定時互斥鎖(std::recursive_timed_mutex
)
- 這是結合了遞迴互斥鎖和定時互斥鎖特點的一種鎖。它允許同一執行緒多次獲取鎖,並且在獲取鎖時可以設定超時時間。
- 當一個執行緒多次獲取這種鎖後,需要釋放相同次數的鎖,鎖才會真正被釋放,並且在獲取鎖的過程中,如果在超時時間內無法獲取,執行緒可以採取相應的措施。
1 #include <iostream> 2 #include <chrono> 3 #include <thread> 4 #include <mutex> 5 6 std::recursive_timed_mutex rtm; 7 8 void recursiveTryLockFunction(int count) { 9 if (rtm.try_lock_for(std::chrono::seconds(1))) { 10 std::cout << "Recursive acquired lock, count = " << count << std::endl; 11 if (count > 0) { 12 recursiveTryLockFunction(count - 1); 13 } 14 rtm.unlock(); 15 } else { 16 std::cout << "Could not recursively acquire lock in time" << std::endl; 17 } 18 } 19 20 int main() { 21 std::thread t(recursiveTryLockFunction, 3); 22 t.join(); 23 return 0; 24 }
自旋鎖(通常用std::atomic_flag
實現)
- 自旋鎖是一種忙等待的鎖機制。當一個執行緒嘗試獲取自旋鎖而鎖已經被佔用時,這個執行緒不會進入阻塞狀態,而是會不斷地檢查(“自旋”)鎖是否已經被釋放。
- 自旋鎖在等待時間較短的情況下可能會有比較好的效能表現,因為它避免了執行緒切換的開銷。但是,如果等待時間過長,由於執行緒一直在佔用 CPU 資源進行檢查,會導致 CPU 資源的浪費。一般在底層程式碼或者對效能要求極高、等待時間預計很短的場景下使用。
1 #include <iostream> 2 #include <atomic> 3 #include <thread> 4 5 std::atomic_flag spinLock = ATOMIC_FLAG_INIT; 6 7 void criticalSection() { 8 while (spinLock.test_and_set()) { 9 // 自旋等待 10 } 11 std::cout << "Entered critical section" << std::endl; 12 // 臨界區操作 13 spinLock.clear(); 14 } 15 16 int main() { 17 std::thread t1(criticalSection); 18 std::thread t2(criticalSection); 19 t1.join(); 20 t2.join(); 21 return 0; 22 }
條件變數(std::condition_variable
)配合互斥鎖用於同步(嚴格來說條件變數不是鎖,但常一起用於執行緒同步場景)
- 條件變數本身不是一種鎖,但它通常和互斥鎖一起使用,用於實現執行緒間的同步。它可以讓一個執行緒等待某個條件滿足後再繼續執行。
- 例如,一個生產者 - 消費者模型中,消費者執行緒在緩衝區為空時可以使用條件變數等待,直到生產者執行緒生產出產品並通知消費者執行緒,這個過程中互斥鎖用於保護緩衝區這個共享資源,條件變數用於實現執行緒間的通訊和同步。
1 #include <iostream> 2 #include <thread> 3 #include <mutex> 4 #include <condition_variable> 5 #include <queue> 6 7 std::mutex mtx; 8 std::condition_variable cv; 9 std::queue<int> buffer; 10 const int bufferSize = 5; 11 12 void producer() { 13 for (int i = 0; i < 10; ++i) { 14 std::unique_lock<std::mutex> lock(mtx); 15 while (buffer.size() == bufferSize) { 16 cv.wait(lock); 17 } 18 buffer.push(i); 19 std::cout << "Produced: " << i << std::endl; 20 cv.notify_all(); 21 } 22 } 23 24 void consumer() { 25 for (int i = 0; i < 10; ++i) { 26 std::unique_lock<std::mutex> lock(mtx); 27 while (buffer.empty()) { 28 cv.wait(lock); 29 } 30 int data = buffer.front(); 31 buffer.pop(); 32 std::cout << "Consumed: " << data << std::endl; 33 cv.notify_all(); 34 } 35 } 36 37 int main() { 38 std::thread producerThread(producer); 39 std::thread consumerThread(consumer); 40 producerThread.join(); 41 consumerThread.join(); 42 return 0; 43 }