在併發程式設計中,讀寫場景和生產者消費者場景是非常經典的兩種問題。它們的基本思路和實現方法在許多應用中都可以找到。下面分別介紹這兩種場景的一些經典問題及其解決方案。
讀寫場景
經典問題
-
多執行緒訪問共享資源:
- 多個執行緒需要同時讀取某個共享資料(例如,配置檔案、快取等),但在某個執行緒需要寫入時,其他執行緒必須等待。
-
統計資訊更新:
- 某個程式模組需要定期更新統計資訊,其他模組在此期間仍然可以讀取這些資訊,而在更新時不允許其他執行緒讀取。
解決方案
- 讀寫鎖:使用
shared_mutex
來實現多個執行緒的併發讀取,而在寫入時使用unique_lock
確保獨佔訪問。
// 虛擬碼示例
class SharedData {
private:
int data;
mutable std::shared_mutex mutex;
public:
// 讀取資料
int read() const {
std::shared_lock<std::shared_mutex> lock(mutex);
return data;
}
// 寫入資料
void write(int value) {
std::unique_lock<std::shared_mutex> lock(mutex);
data = value;
}
};
生產者消費者場景
經典問題
-
訊息佇列:
- 訊息生產者將訊息放入佇列,消費者從佇列中取出訊息進行處理。需要保證在佇列為空時消費者等待,而在佇列滿時生產者等待。
-
任務排程:
- 任務生成器將任務新增到任務佇列中,而工作執行緒從任務佇列中取出任務並執行。需要處理併發安全和阻塞條件。
解決方案
- 條件變數:結合
mutex
和condition_variable
來實現生產者和消費者之間的協調。
#include <iostream>
#include <queue>
#include <thread>
#include <condition_variable>
template <typename T>
class BoundedBuffer {
private:
std::queue<T> buffer;
size_t max_size;
std::mutex mtx;
std::condition_variable not_empty;
std::condition_variable not_full;
public:
BoundedBuffer(size_t size) : max_size(size) {}
void produce(T item) {
std::unique_lock<std::mutex> lock(mtx);
not_full.wait(lock, [this] { return buffer.size() < max_size; });
buffer.push(item);
not_empty.notify_one();
}
T consume() {
std::unique_lock<std::mutex> lock(mtx);
not_empty.wait(lock, [this] { return !buffer.empty(); });
T item = buffer.front();
buffer.pop();
not_full.notify_one();
return item;
}
};
總結
- 讀寫場景強調了多個執行緒如何有效地訪問共享資源,尤其是在讀取操作多於寫入操作時。使用讀寫鎖是一種常見的解決方案。
- 生產者消費者場景則側重於在多執行緒環境中協調資料的生產和消費,確保資料的安全性和系統的高效性。利用條件變數和互斥量是解決此問題的經典方式。
這兩種場景廣泛應用於資料庫、快取、實時處理系統等各種領域,是併發程式設計中的重要概念。