在 C++ 中,實現執行緒同步主要有以下幾種常見方法

MarsCactus發表於2024-11-09
  1. 互斥鎖(Mutex)
    原理:
    互斥鎖用於保護共享資源,確保在同一時刻只有一個執行緒能夠訪問被保護的資源。當一個執行緒想要訪問共享資源時,它首先嚐試獲取互斥鎖。如果鎖已經被其他執行緒持有,那麼該執行緒會被阻塞,直到鎖被釋放。一旦獲取到鎖,執行緒就可以安全地訪問共享資源,訪問結束後再釋放鎖,以便其他執行緒能夠獲取鎖並訪問資源。
    示例程式碼:
    cpp
    複製
#include <iostream>
#include <thread>
#include <mutex>

// 定義一個互斥鎖
std::mutex mutex_f;

// 共享資源,這裡是一個簡單的整數變數
int shared_resource = 0;

// 執行緒函式,用於對共享資源進行操作
void increment_resource() {
    // 獲取互斥鎖
    std::lock_guard<std::mutex> guard(mutex_f);

    // 對共享資源進行操作
    shared_resource++;

    // 釋放互斥鎖(由lock_guard的解構函式自動完成)
}

int main() {
    // 建立兩個執行緒
    std::thread t1(increment_resource);
    std::thread t2(increment_resource);

    // 等待執行緒完成
    t1.join();
    t2.join();

    // 輸出共享資源的值
    std::cout << "Shared resource value: " << shared_resource << std::endl;

    return 0;
}

在上述示例中,透過std::lock_guard來自動管理互斥鎖的獲取和釋放,確保在increment_resource函式中對共享資源shared_resource的操作是執行緒安全的。
2. 條件變數(Condition Variable)
原理:
條件變數通常與互斥鎖一起使用,用於讓一個執行緒等待某個特定條件的滿足。執行緒首先獲取互斥鎖,然後檢查條件是否滿足。如果條件不滿足,執行緒會釋放互斥鎖並進入等待狀態,直到被其他執行緒透過通知機制喚醒。被喚醒後,執行緒會再次獲取互斥鎖並重新檢查條件是否滿足,只有當條件滿足時才會繼續執行後續操作。
示例程式碼:
cpp
複製

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

// 定義互斥鎖和條件變數
std::mutex mutex_g;
std::condition_variable condition_g;

// 共享資源,這裡是一個簡單的整數變數
int shared_resource_g = 0;

// 用於標識條件是否滿足的標誌
bool condition_met = false;

// 執行緒函式,用於增加共享資源的值並在滿足條件時通知其他執行緒
void increment_and_notify() {
    // 獲取互斥鎖
    std::lock_guard<std::mutex> guard(mutex_g);

    // 對共享資源進行操作
    shared_resource_g++;

    // 當共享資源的值達到一定程度時,設定條件滿足標誌並通知其他執行緒
    if (shared_resource_g >= 5) {
        condition_met = true;
        condition_g.notify_all();
    }
}

// 執行緒函式,用於等待條件滿足
void wait_for_condition() {
    // 獲取互斥鎖
    std::unique_lock<std::mutex> lock(mutex_g);

    // 等待條件滿足,當條件不滿足時釋放互斥鎖並進入等待狀態
    condition_g.wait(lock, []{ return condition_met; });

    // 條件滿足後,輸出相關資訊
    std::cout << "Condition met, shared resource value: " << shared_resource_g << std::endl;
}

int main() {
    // 建立兩個執行緒
    std::thread t1(increment_and_notify);
    std::thread t2(wait_for_condition);

    // 等待執行緒完成
    t1.join();
    t2.join();

    return 0;
}

在上述示例中,increment_and_notify執行緒負責增加共享資源的值,並在滿足特定條件(shared_resource_g >= 5)時通知其他執行緒。wait_for_condition執行緒則透過條件變數等待條件滿足,在條件滿足後輸出相關資訊。
3. 訊號量(Semaphore)
原理:
訊號量是一種用於控制對共享資源訪問的計數器。它有一個初始值,表示可用資源的數量。當一個執行緒想要訪問共享資源時,它會先嚐試獲取訊號量。如果訊號量的值大於 0,執行緒就可以獲取訊號量(即將訊號量的值減 1)並訪問共享資源。訪問結束後,執行緒會釋放訊號量(即將訊號量的值加 1),以便其他執行緒能夠獲取訊號量並訪問資源。如果訊號量的值為 0,執行緒會被阻塞,直到有其他執行緒釋放訊號量。
示例程式碼:
cpp
複製

#include <iostream>
#include <thread>
#include <semaphore.h>

// 定義一個訊號量,並設定初始值為3,表示有3個可用資源
sem_t semaphore_h;

// 共享資源,這裡簡單地用一個整數變數表示
int shared_resource_h = 0;

// 執行緒函式,用於獲取訊號量、訪問共享資源並釋放訊號量
void access_shared_resource() {
    // 獲取訊號量
    sem_wait(&semaphore_h);

    // 對共享資源進行操作
    shared_resource_h++;

    // 釋放訊號量
    sem_post(&semaphore_h);

    // 輸出相關資訊
    std::cout << "Accessed shared resource, current value: " << shared_resource_h << std::endl;
}

int main() {
    // 初始化訊號量
    sem_init(&semaphore_h, 0, 3);

    // 建立三個執行緒
    std::thread t1(access_shared_resource);
    std::thread t2(access_shared_resource);
    std::thread t3(access_shared_resource);

    // 等待執行緒完成
    t1.join();
    t2.join();
    t3.join();

    // 銷燬訊號量
    sem_destroy(&semaphore_h);

    return 0;
}

在上述示例中,透過sem_wait獲取訊號量,sem_post釋放訊號量,實現了對共享資源shared_resource_h的執行緒安全訪問。
4. 讀寫鎖(Read-Write Lock)
原理:
讀寫鎖用於區分對共享資源的讀操作和寫操作。多個執行緒可以同時進行讀操作,因為讀操作不會改變共享資源的狀態。但是,當有一個執行緒進行寫操作時,其他執行緒無論是讀還是寫都不能同時進行,必須等待寫操作完成。讀寫鎖透過維護讀鎖和寫鎖的狀態來實現這種區分。
示例程式碼:
cpp
複製

#include <iostream>
#include <thread>
#include <shared_mutex>

// 定義一個讀寫鎖
std::shared_mutex read_write_lock_i;

// 共享資源,這裡是一個簡單的整數變數
int shared_resource_i = 0;

// 執行緒函式,用於進行讀操作
void read_shared_resource() {
    // 獲取讀鎖
    std::shared_lock<std::shared_mutex> read_lock(read_write_lock_i);

    // 對共享資源進行讀操作
    std::cout << "Read shared resource value: " << shared_resource_i << std::endl;

    // 釋放讀鎖(由shared_lock的解構函式自動完成)
}

// 執行緒函式,用於進行寫操作
void write_shared_resource() {
    // 獲取寫鎖
    std::unique_lock<std::shared_mutex> write_lock(read_write_lock_i);

    // 對共享資源進行寫操作
    shared_resource_i++;

    // 釋放寫鎖(由unique_lock的解構函式自動完成)
}

int main() {
    // 建立三個執行緒進行讀操作,一個執行緒進行寫操作
    std::thread t1(read_shared_resource);
    std::thread t2(read_shared_resource);
    std::thread t3(read_shared_resource);
    std::thread t4(write_shared_resource);

    // 等待執行緒完成
    t1.join();
    t2.join();
    t3.join();
    t4.join();

    return 0;
}

在上述示例中,透過std::shared_lock獲取讀鎖進行讀操作,透過std::unique_lock獲取寫鎖進行寫操作,實現了對共享資源shared_resource_i的讀寫操作的執行緒安全管理。
5. 原子型別(Atomic Types)
原理:
原子型別是 C++11 及以後引入的一種資料型別,它允許對其進行原子操作,即這些操作在多執行緒環境下是不可分割的,要麼完整執行,要麼不執行,不存在中間狀態被其他執行緒看到的情況。常見的原子型別有std::atomic、std::atomic等。透過使用原子型別來定義共享資源,可以避免使用傳統的同步機制(如互斥鎖等)來實現執行緒安全。
示例程式碼:
cpp
複製

#include <iostream>
#include <thread>
#include <atomic>

// 定義一個原子型別的整數變數
std::atomic<int> atomic_shared_resource_j(0);

// 執行緒函式,用於對原子型別的共享資源進行操作
void increment_atomic_resource() {
    // 對原子型別的共享資源進行操作
    atomic_shared_resource_j++;

    // 輸出相關資訊
    std::cout << "Atomic shared resource value: " << atomic_shared_resource_j.load() << std::endl;
}

int main() {
    // 建立兩個執行緒
    std::thread t1(increment_atomic_resource);
    std::thread t2(increment_atomic_resource);

    // 等待執行緒完成
    t1.join();
    t2.join();

    return 0;
}

在上述示例中,透過使用std::atomic定義共享資源atomic_shared_resource_j,並直接對其進行原子操作(如++操作),實現了執行緒安全的資源管理。

相關文章