C++中線程同步與互斥的四種方式介紹及對比詳解
引言
在C++中,當(dāng)兩個(gè)或更多的線程需要訪問共享數(shù)據(jù)時(shí),就會出現(xiàn)線程安全問題。這是因?yàn)?,如果沒有適當(dāng)?shù)耐綑C(jī)制,一個(gè)線程可能在另一個(gè)線程還沒有完成對數(shù)據(jù)的修改就開始訪問數(shù)據(jù),這將導(dǎo)致數(shù)據(jù)的不一致性和程序的不可預(yù)測性。為了解決這個(gè)問題,C++提供了多種線程同步和互斥的機(jī)制。
1. 互斥量(Mutex)
互斥量是一種同步機(jī)制,用于防止多個(gè)線程同時(shí)訪問共享資源。在C++中,可以使用std::mutex類來創(chuàng)建互斥量。
#include <thread>
#include <mutex>
std::mutex mtx; // 全局互斥量
int shared_data = 0; // 共享數(shù)據(jù)
void thread_func() {
for (int i = 0; i < 10000; ++i) {
mtx.lock(); // 獲取互斥量的所有權(quán)
++shared_data; // 修改共享數(shù)據(jù)
mtx.unlock(); // 釋放互斥量的所有權(quán)
}
}
int main() {
std::thread t1(thread_func);
std::thread t2(thread_func);
t1.join();
t2.join();
std::cout << shared_data << std::endl; // 輸出20000
return 0;
}
在上述代碼中,我們創(chuàng)建了一個(gè)全局互斥量mtx和一個(gè)共享數(shù)據(jù)shared_data。然后,我們在thread_func函數(shù)中使用mtx.lock()和mtx.unlock()來保護(hù)對shared_data的訪問,確保在任何時(shí)候只有一個(gè)線程可以修改shared_data。
2. 鎖(Lock)
除了直接使用互斥量,C++還提供了std::lock_guard和std::unique_lock兩種鎖,用于自動管理互斥量的所有權(quán)。
#include <thread>
#include <mutex>
std::mutex mtx; // 全局互斥量
int shared_data = 0; // 共享數(shù)據(jù)
void thread_func() {
for (int i = 0; i < 10000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 創(chuàng)建鎖,自動獲取互斥量的所有權(quán)
++shared_data; // 修改共享數(shù)據(jù)
// 鎖在離開作用域時(shí)自動釋放互斥量的所有權(quán)
}
}
int main() {
std::thread t1(thread_func);
std::thread t2(thread_func);
t1.join();
t2.join();
std::cout << shared_data << std::endl; // 輸出20000
return 0;
}
在上述代碼中,我們使用std::lock_guard來自動管理互斥量的所有權(quán)。當(dāng)創(chuàng)建std::lock_guard對象時(shí),它會自動獲取互斥量的所有權(quán),當(dāng)std::lock_guard對象離開作用域時(shí),它會自動釋放互斥量的所有權(quán)。這樣,我們就不需要手動調(diào)用mtx.lock()和mtx.unlock(),可以避免因忘記釋放互斥量而導(dǎo)致的死鎖。
3. 條件變量(Condition Variable)
條件變量是一種同步機(jī)制,用于在多個(gè)線程之間同步條件的變化。在C++中,可以使用std::condition_variable類來創(chuàng)建條件變量。
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx; // 全局互斥量
std::condition_variable cv; // 全局條件變量
bool ready = false; // 共享?xiàng)l件
void print_id(int id) {
std::unique_lock<std::mutex> lock(mtx); // 創(chuàng)建鎖,自動獲取互斥量的所有權(quán)
while (!ready) { // 如果條件不滿足
cv.wait(lock); // 等待條件變量的通知
}
// 當(dāng)收到條件變量的通知,且條件滿足時(shí),繼續(xù)執(zhí)行
std::cout << "thread " << id << '\n';
}
void go() {
std::unique_lock<std::mutex> lock(mtx); // 創(chuàng)建鎖,自動獲取互斥量的所有權(quán)
ready = true; // 修改共享?xiàng)l件
cv.notify_all(); // 通知所有等待的線程
}
int main() {
std::thread threads[10];
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(print_id, i);
std::cout << "10 threads ready to race...\n";
go(); // 開始比賽
for (auto& th : threads) th.join();
return 0;
}
在上述代碼中,我們創(chuàng)建了一個(gè)全局互斥量mtx、一個(gè)全局條件變量cv和一個(gè)共享?xiàng)l件ready。然后,我們在print_id函數(shù)中使用cv.wait(lock)來等待條件變量的通知,當(dāng)收到條件變量的通知,且條件滿足時(shí),繼續(xù)執(zhí)行。在go函數(shù)中,我們修改共享?xiàng)l件,并使用cv.notify_all()來通知所有等待的線程。
4. 原子操作(Atomic Operation)
原子操作是一種特殊的操作,它可以在多線程環(huán)境中安全地對數(shù)據(jù)進(jìn)行讀寫,而無需使用互斥量或鎖。在C++中,可以使用std::atomic模板類來創(chuàng)建原子類型。
#include <thread>
#include <atomic>
std::atomic<int> shared_data(0); // 共享數(shù)據(jù)
void thread_func() {
for (int i = 0; i < 10000; ++i) {
++shared_data; // 原子操作
}
}
int main() {
std::thread t1(thread_func);
std::thread t2(thread_func);
t1.join();
t2.join();
std::cout << shared_data << std::endl; // 輸出20000
return 0;
}
在上述代碼中,我們創(chuàng)建了一個(gè)原子類型的共享數(shù)據(jù)shared_data。然后,我們在thread_func函數(shù)中使用++shared_data來進(jìn)行原子操作,這樣,我們就不需要使用互斥量或鎖,也可以保證在任何時(shí)候只有一個(gè)線程可以修改shared_data。
5. 對比
| 策略 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|
| 單一全局互斥量 | 簡單 | 可能導(dǎo)致嚴(yán)重的性能問題,降低并發(fā)性 |
| 多個(gè)互斥量 | 提高并發(fā)性 | 增加程序復(fù)雜性,需要避免死鎖 |
| 原子操作 | 提高并發(fā)性,避免互斥量開銷 | 增加程序復(fù)雜性,需要理解和使用原子操作 |
| 讀寫鎖 | 提高并發(fā)性,特別是讀操作多于寫操作時(shí) | 增加程序復(fù)雜性,需要管理讀寫鎖,需要避免死鎖 |
案例舉例
假設(shè)我們正在開發(fā)一個(gè)在線聊天 服務(wù)器,需要處理大量的并發(fā)連接。每個(gè)連接都有一個(gè)關(guān)聯(lián)的用戶對象,用戶對象包含了用戶的狀態(tài)信息,如用戶名、在線狀態(tài)等。
在這種情況下,我們可以使用多個(gè)互斥量的策略。我們可以將用戶對象劃分為幾個(gè)組,每個(gè)組有一個(gè)關(guān)聯(lián)的互斥量。當(dāng)一個(gè)線程需要訪問一個(gè)用戶對象時(shí),它只需要鎖定該用戶對象所在組的互斥量,而不是所有的用戶對象。這樣,不同的線程可以同時(shí)訪問不同的用戶對象,從而提高并發(fā)性。
同時(shí),我們也可以使用讀寫鎖的策略。因?yàn)樵诖蠖鄶?shù)情況下,線程只需要讀取用戶的狀態(tài)信息,而不需要修改。所以,我們可以使用讀寫鎖,允許多個(gè)線程同時(shí)讀取用戶對象,但在修改用戶對象時(shí)需要獨(dú)占鎖。
在實(shí)踐中,我們可能需要結(jié)合使用這兩種策略,以達(dá)到最佳的效果。
6. 更進(jìn)一步:原子操作+鎖
原子操作和鎖是兩種不同的線程同步機(jī)制,它們可以單獨(dú)使用,也可以一起使用,具體取決于你的應(yīng)用場景。
原子操作是一種低級的同步機(jī)制,它可以保證對單個(gè)內(nèi)存位置的讀寫操作是原子的,即在任何時(shí)候只有一個(gè)線程可以對內(nèi)存位置進(jìn)行操作。原子操作通常用于實(shí)現(xiàn)高級的同步機(jī)制,如鎖和條件變量。
鎖是一種高級的同步機(jī)制,它可以保證對一段代碼或多個(gè)內(nèi)存位置的訪問是原子的,即在任何時(shí)候只有一個(gè)線程可以執(zhí)行被鎖保護(hù)的代碼或訪問被鎖保護(hù)的內(nèi)存位置。
如果你在使用鎖的同時(shí)還使用原子操作,那么你需要確保你的代碼正確地理解和使用這兩種同步機(jī)制。例如,如果你在一個(gè)被鎖保護(hù)的代碼段中使用原子操作,那么你需要確保原子操作不會違反鎖的語義,即在任何時(shí)候只有一個(gè)線程可以執(zhí)行被鎖保護(hù)的代碼。
以下是一個(gè)使用原子操作和鎖的例子:
#include <thread>
#include <mutex>
#include <atomic>
std::mutex mtx; // 全局互斥量
std::atomic<int> counter(0); // 原子計(jì)數(shù)器
void thread_func() {
for (int i = 0; i < 10000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 獲取互斥量的所有權(quán)
++counter; // 原子操作
// 鎖在離開作用域時(shí)自動釋放互斥量的所有權(quán)
}
}
int main() {
std::thread t1(thread_func);
std::thread t2(thread_func);
t1.join();
t2.join();
std::cout << counter << std::endl; // 輸出20000
return 0;
}
在上述代碼中,我們使用std::lock_guard來獲取互斥量的所有權(quán),然后使用++counter來進(jìn)行原子操作。這樣,我們既保證了在任何時(shí)候只有一個(gè)線程可以執(zhí)行被鎖保護(hù)的代碼,也保證了對counter的操作是原子的。
總的來說,原子操作和鎖可以一起使用,但你需要確保你的代碼正確地理解和使用這兩種同步機(jī)制。
總結(jié)
在C++中,當(dāng)兩個(gè)或更多的線程需要訪問共享數(shù)據(jù)時(shí),可以使用互斥量、鎖、條件變量和原子操作等多種線程同步和互斥的機(jī)制來保證線程安全。選擇哪種機(jī)制,取決于具體的應(yīng)用場景和需求。
以上就是C++中線程同步與互斥的四種方式介紹及對比詳解的詳細(xì)內(nèi)容,更多關(guān)于C++線程同步與互斥方式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++使用模板實(shí)現(xiàn)單鏈表(類外實(shí)現(xiàn))
這篇文章主要為大家詳細(xì)介紹了C++使用模板實(shí)現(xiàn)單鏈表的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
解決了個(gè)困擾了2天的問題,定點(diǎn)運(yùn)算問題
本文主要講解定點(diǎn)運(yùn)算問題,需要的朋友可以參考一下。2016-06-06
C++實(shí)現(xiàn)LeetCode(179.最大組合數(shù))
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(179.最大組合數(shù)),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
關(guān)于vector迭代器失效的幾種情況總結(jié)
下面小編就為大家?guī)硪黄P(guān)于vector迭代器失效的幾種情況總結(jié)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-12-12
C語言?超詳細(xì)講解算法的時(shí)間復(fù)雜度和空間復(fù)雜度
算法復(fù)雜度分為時(shí)間復(fù)雜度和空間復(fù)雜度。其作用:?時(shí)間復(fù)雜度是度量算法執(zhí)行的時(shí)間長短;而空間復(fù)雜度是度量算法所需存儲空間的大小2022-03-03
C語言三分鐘精通時(shí)間復(fù)雜度與空間復(fù)雜度
算法復(fù)雜度分為時(shí)間復(fù)雜度和空間復(fù)雜度。其作用:?時(shí)間復(fù)雜度是度量算法執(zhí)行的時(shí)間長短;而空間復(fù)雜度是度量算法所需存儲空間的大小2022-02-02

