C++怎么使用多線程同步 C++多線程同步的幾種機制對比

c++++線程同步通過多種機制確保線程安全;1.互斥鎖(mutex)用于保護共享資源,如代碼中使用mtx.lock()和mtx.unlock()控制counter訪問;2.條件變量(condition variable)用于線程等待特定條件,如cv.wait()和cv.notify_one()配合unique_lock實現線程通信;3.原子操作(atomic operations)提供輕量級同步,如std::atomic保證counter++的原子性;4.讀寫鎖(read-write lock)允許多個線程同時讀取,如std::shared_mutex配合shared_lock和unique_lock實現讀寫控制;5.信號量(semaphore)控制資源訪問數量,如std::counting_semaphore管理最多三個并發線程。選擇時應根據場景:互斥鎖適合保護共享數據,條件變量適合等待條件觸發,原子操作適合簡單計數器,讀寫鎖適合讀多寫少,信號量適合資源池管理。避免死鎖的方法包括避免嵌套鎖、使用超時鎖、減小鎖粒度、資源排序等。c++11后新增了recursive_mutex、timed_mutex、future/promise工具提升并發編程能力。原子操作適用于簡單操作,互斥鎖則更適合復雜資源保護。

C++怎么使用多線程同步 C++多線程同步的幾種機制對比

C++多線程同步,簡單來說,就是讓多個線程能夠安全地共享資源,避免出現數據競爭和死鎖等問題。要做到這一點,你需要用到一些同步機制,比如互斥鎖、條件變量、原子操作等等。

C++怎么使用多線程同步 C++多線程同步的幾種機制對比

互斥鎖(Mutex) 最常用的同步機制之一。想象一下,你和你的朋友想同時用一支筆,但筆只有一個,這時候就需要互斥鎖。

C++怎么使用多線程同步 C++多線程同步的幾種機制對比

#include <iostream> #include <thread> #include <mutex>  std::mutex mtx; // 定義一個互斥鎖 int counter = 0;  void increment() {     for (int i = 0; i < 10000; ++i) {         mtx.lock(); // 加鎖         counter++;         mtx.unlock(); // 解鎖     } }  int main() {     std::thread t1(increment);     std::thread t2(increment);      t1.join();     t2.join();      std::cout << "Counter value: " << counter << std::endl; // 預期結果:20000     return 0; }

這段代碼里,mtx.lock() 就像是你拿起了筆,mtx.unlock() 就像是你用完放下了筆。只有拿到鎖的線程才能訪問 counter 變量。

C++怎么使用多線程同步 C++多線程同步的幾種機制對比

條件變量(Condition Variable)

立即學習C++免費學習筆記(深入)”;

互斥鎖可以保證資源的安全訪問,但如果線程需要等待某個特定條件滿足才能繼續執行,互斥鎖就不夠用了。這時候就需要條件變量。

#include <iostream> #include <thread> #include <mutex> #include <condition_variable>  std::mutex mtx; std::condition_variable cv; bool ready = false;  void worker_thread() {     std::unique_lock<std::mutex> lock(mtx); // 自動解鎖的互斥鎖     cv.wait(lock, []{ return ready; }); // 等待條件滿足     std::cout << "Worker thread is processing..." << std::endl; }  void signal_ready() {     std::this_thread::sleep_for(std::chrono::seconds(1));     {         std::lock_guard<std::mutex> lock(mtx);         ready = true;     }     cv.notify_one(); // 通知一個等待的線程 }  int main() {     std::thread worker(worker_thread);     std::thread signaler(signal_ready);      worker.join();     signaler.join();      return 0; }

cv.wait(lock, []{ return ready; }) 讓線程進入等待狀態,直到 ready 變為 true。cv.notify_one() 用于喚醒一個等待的線程。 std::unique_lock 是一個RAII風格的鎖,離開作用域會自動解鎖,避免忘記解鎖導致死鎖。

原子操作(Atomic Operations)

原子操作是一種更輕量級的同步機制,適用于簡單的計數器或者標志位。原子操作保證操作的原子性,即不可分割。

#include <iostream> #include <thread> #include <atomic>  std::atomic<int> counter(0);  void increment() {     for (int i = 0; i < 10000; ++i) {         counter++; // 原子操作     } }  int main() {     std::thread t1(increment);     std::thread t2(increment);      t1.join();     t2.join();      std::cout << "Counter value: " << counter << std::endl; // 預期結果:20000     return 0; }

std::atomic counter(0) 定義了一個原子整數。counter++ 是一個原子操作,保證了并發訪問的安全性。

讀寫鎖(Read-Write Lock)

讀寫鎖允許多個線程同時讀取共享資源,但只允許一個線程寫入共享資源。這種鎖適用于讀多寫少的場景。C++標準庫并沒有直接提供讀寫鎖,但你可以使用第三方庫,例如 Boost。

#include <iostream> #include <thread> #include <shared_mutex> // C++17引入  std::shared_mutex rw_mutex; int data = 0;  void reader() {     for (int i = 0; i < 5; ++i) {         std::shared_lock<std::shared_mutex> lock(rw_mutex); // 共享鎖         std::cout << "Reader: " << data << std::endl;         std::this_thread::sleep_for(std::chrono::milliseconds(100));     } }  void writer() {     for (int i = 0; i < 3; ++i) {         std::unique_lock<std::shared_mutex> lock(rw_mutex); // 獨占鎖         data++;         std::cout << "Writer: " << data << std::endl;         std::this_thread::sleep_for(std::chrono::milliseconds(200));     } }  int main() {     std::thread t1(reader);     std::thread t2(writer);     std::thread t3(reader);      t1.join();     t2.join();     t3.join();      return 0; }

std::shared_lock 用于獲取共享鎖,允許多個線程同時讀取數據。std::unique_lock 用于獲取獨占鎖,只允許一個線程寫入數據。C++17 引入了 std::shared_mutex,簡化了讀寫鎖的使用。

信號量(Semaphore)

信號量是一種更通用的同步機制,可以控制對有限資源的訪問。

#include <iostream> #include <thread> #include <semaphore>  std::counting_semaphore<3> semaphore(3); // 允許最多3個線程同時訪問  void worker(int id) {     semaphore.acquire(); // 獲取信號量     std::cout << "Thread " << id << " is working..." << std::endl;     std::this_thread::sleep_for(std::chrono::seconds(1));     std::cout << "Thread " << id << " is done." << std::endl;     semaphore.release(); // 釋放信號量 }  int main() {     std::thread t1(worker, 1);     std::thread t2(worker, 2);     std::thread t3(worker, 3);     std::thread t4(worker, 4);     std::thread t5(worker, 5);      t1.join();     t2.join();     t3.join();     t4.join();     t5.join();      return 0; }

std::counting_semaphore semaphore(3) 定義了一個初始值為 3 的信號量。semaphore.acquire() 用于獲取信號量,如果信號量的值為 0,線程將進入等待狀態。semaphore.release() 用于釋放信號量,使信號量的值加 1。

如何選擇合適的同步機制?

選擇合適的同步機制取決于你的具體需求。

  • 互斥鎖:適用于保護共享資源,確保同一時間只有一個線程可以訪問。
  • 條件變量:適用于線程需要等待特定條件滿足才能繼續執行的場景。
  • 原子操作:適用于簡單的計數器或者標志位,性能較高。
  • 讀寫鎖:適用于讀多寫少的場景,可以提高并發性能。
  • 信號量:適用于控制對有限資源的訪問。

多線程同步可能遇到的問題有哪些?

多線程同步雖然能解決并發訪問的問題,但也可能引入一些新的問題。

  • 死鎖:當多個線程互相等待對方釋放資源時,就會發生死鎖。
  • 活鎖:線程不斷重試一個操作,但由于其他線程的干擾,始終無法成功。
  • 饑餓:某個線程長時間無法獲得所需的資源,導致無法執行。
  • 優先級反轉:高優先級線程等待低優先級線程釋放資源,導致高優先級線程的執行被延遲。

如何避免死鎖?

死鎖是多線程編程中最常見的問題之一,以下是一些避免死鎖的常用方法:

  • 避免嵌套鎖:盡量避免在一個鎖的保護范圍內再請求另一個鎖。如果必須使用嵌套鎖,確保所有線程以相同的順序獲取鎖。
  • 使用超時鎖:try_lock 可以嘗試獲取鎖,如果一段時間內無法獲取,則返回失敗,避免永久等待。
  • 鎖的粒度:減小鎖的粒度,盡量只保護需要同步的最小代碼塊,減少鎖的競爭。
  • 資源排序:對所有需要鎖定的資源進行排序,所有線程按照相同的順序獲取鎖。

C++11 之后有哪些新的同步工具

C++11 引入了許多新的同步工具,例如:

  • std::mutex:互斥鎖。
  • std::recursive_mutex:遞歸鎖,允許同一個線程多次獲取同一個鎖。
  • std::timed_mutex:定時鎖,可以設置獲取鎖的超時時間。
  • std::condition_variable:條件變量。
  • std::atomic:原子操作。
  • std::future 和 std::promise:用于異步編程,可以方便地獲取異步操作的結果。
  • std::shared_mutex (C++17):讀寫鎖。
  • std::counting_semaphore (C++20):信號量。

什么時候應該使用原子操作而不是互斥鎖?

原子操作通常比互斥鎖更輕量級,性能更高,但原子操作只能用于簡單的操作,例如計數器或者標志位的更新。如果需要保護復雜的共享資源,或者需要執行多個操作的原子性,應該使用互斥鎖。

總的來說,選擇合適的同步機制,并正確地使用它們,是編寫高效、安全的并發程序的關鍵。

? 版權聲明
THE END
喜歡就支持一下吧
點贊5 分享