Mutex(互斥鎖)、Condition Variables(條件變量)和Semaphore(信號量)是多線程編程中常用的同步原語,它們各自有不同的功能,但也可以相互配合使用。以下是它們之間的關係和主要區別:
互補性:這三者可以一起使用,以實現更複雜的同步需求。例如,可以使用互斥鎖來保護共享資源的訪問,使用條件變量來在某些條件下讓線程等待,使用信號量來控制同時訪問的線程數量。
典型用法:
以下是 Mutex、Condition Variables 和 Semaphore 的獨立範例程式碼。
// mutex.cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int sharedResource = 0;
void increment() {
    for (int i = 0; i < 10000; ++i) {
        mtx.lock(); // 獲取鎖
        ++sharedResource; // 訪問共享資源
        mtx.unlock(); // 釋放鎖
    }
}
int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    std::cout << "Final value: " << sharedResource << std::endl; 
    // 因為兩個線程各自增加了 10000 次,所以是 20000。
    return 0;
}

使用互斥鎖來保護共享資源,避免競爭條件,並使用兩個線程同時增加同一個變量的值。
// cv.cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::queue<int> buffer;
std::mutex mtx;
std::condition_variable cv;
void producer() {
    for (int i = 0; i < 10; ++i) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            buffer.push(i);
            std::cout << "Produced: " << i << std::endl;
        }
        cv.notify_one(); // 通知消費者
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}
void consumer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return !buffer.empty(); }); // 等待條件變量
        int item = buffer.front();
        buffer.pop();
        std::cout << "Consumed: " << item << std::endl;
    }
}
int main() {
    std::thread prodThread(producer);
    std::thread consThread(consumer);
    prodThread.join();
    consThread.join();
    return 0;
}

這段程式碼實現了一個使用條件變量的生產者-消費者模型。與之前的例子相比,這裡使用了 std::condition_variable 來更有效地處理生產者和消費者之間的同步。以下是程式碼的詳細解釋:
標頭檔:
#include <iostream>:用於輸入輸出。#include <thread>:用於多線程支持。#include <mutex>:用於互斥鎖,保護共享資源。#include <condition_variable>:用於條件變量,實現線程間的通知。#include <queue>:用於佇列資料結構。全域變數:
std::queue<int> buffer;:用於存儲生產的資料。std::mutex mtx;:互斥鎖,用於保護對佇列的訪問。std::condition_variable cv;:條件變量,用於通知消費者有資料可消費。producer():
std::lock_guard<std::mutex> lock(mtx); 獲取鎖,然後將資料放入佇列。cv.notify_one(); 通知消費者有新資料可用。consumer():
std::unique_lock<std::mutex> lock(mtx); 獲取鎖,然後使用 cv.wait(lock, [] { return !buffer.empty(); }); 等待條件變量,直到佇列不再空。cv.wait(lock, [] { return !buffer.empty(); }); 是一個條件等待,只有當 buffer 非空時,消費者才會從等待中醒來。這段程式碼展示了如何使用 C++ 的多線程功能和條件變量來實現一個高效的生產者-消費者模型。生產者和消費者之間的協調通過條件變量來實現,這樣可以提高資源的使用效率。
// semaphore.cpp
#include <iostream>
#include <thread>
#include <semaphore.h>
#include <queue>
#include <mutex>
#include <chrono>
const int MAX_QUEUE_SIZE = 5;
std::queue<int> buffer;
std::mutex mtx;
sem_t empty; // 用於表示空位的信號量
sem_t full;  // 用於表示佇列中項目的信號量
void producer() {
    std::cout << "Producer starting..." << std::endl;
    for (int i = 0; i < 10; ++i) {
        sem_wait(&empty); // 等待有空位
        {
            std::lock_guard<std::mutex> lock(mtx);
            buffer.push(i);
            std::cout << "Produced: " << i << std::endl;
        }
        sem_post(&full); // 增加佇列中項目的計數
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}
void consumer() {
    std::cout << "Consumer starting..." << std::endl;
    for (int i = 0; i < 10; ++i) {
        sem_wait(&full); // 等待有項目可消費
        {
            std::lock_guard<std::mutex> lock(mtx);
            int item = buffer.front();
            buffer.pop();
            std::cout << "Consumed: " << item << std::endl;
        }
        sem_post(&empty); // 增加空位的計數
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
}
int main() {
    sem_init(&empty, 0, MAX_QUEUE_SIZE); // 初始化空位信號量
    sem_init(&full, 0, 0); // 初始化佇列中項目的信號量為0
    std::thread prodThread(producer);
    std::thread consThread(consumer);
    prodThread.join();
    consThread.join();
    sem_destroy(&empty); // 銷毀信號量
    sem_destroy(&full); // 銷毀信號量
    return 0;
}

這段程式碼實現了一個簡單的生產者-消費者模型,使用 C++ 的多線程功能來模擬生產者生成資料並將其放入佇列,消費者則從佇列中取出資料進行消費。以下是程式碼的詳細說明:
標頭檔:
#include <iostream>:用於輸入輸出。#include <thread>:用於多線程支持。#include <semaphore.h>:用於信號量的操作。#include <queue>:用於佇列資料結構。#include <mutex>:用於互斥鎖,保護共享資源。#include <chrono>:用於時間相關的功能。全域變數:
const int MAX_QUEUE_SIZE = 5;:佇列的最大容量。std::queue<int> buffer;:用於存儲生產的資料。std::mutex mtx;:互斥鎖,用於保護對佇列的訪問。sem_t empty;:信號量,用於表示佇列中的空位。sem_t full;:信號量,用於表示佇列中已有的項目。producer():
sem_wait(&empty); 等待有空位。std::lock_guard<std::mutex> lock(mtx); 獲取鎖,然後將資料放入佇列。sem_post(&full); 增加佇列中項目的計數。consumer():
sem_wait(&full); 等待有項目可消費。std::lock_guard<std::mutex> lock(mtx); 獲取鎖,然後從佇列中取出資料。sem_post(&empty); 增加空位的計數。main():
sem_init(&empty, 0, MAX_QUEUE_SIZE);:設定空位信號量的初始值為最大佇列容量。sem_init(&full, 0, 0);:設定佇列中項目的信號量初始值為 0。std::thread prodThread(producer);
std::thread consThread(consumer);
join() 等待兩個執行緒完成。std::this_thread::sleep_for 來模擬生產和消費的延遲,這樣可以更清楚地看到生產和消費的過程。這段程式碼是一個經典的生產者-消費者問題的實現,展示了如何使用多線程和信號量來解決資源共享的問題。
Mutex 範例:這個範例展示了如何使用 Mutex 來保護對共享資源的訪問,確保兩個線程同時增量共享變量不會導致競爭條件。
Condition Variables 範例:這個範例展示了生產者和消費者之間的協作,使用條件變量來等待和通知。
Semaphore 範例:這個範例展示了如何使用信號量來限制同時訪問的資源數量,確保不會超過緩衝區的容量。