C++ 互斥鎖原理以及實(shí)際使用介紹
一、互斥原理(mutex)
互斥鎖可以確保在任何時(shí)候只有一個(gè)線程能夠進(jìn)入臨界區(qū)。當(dāng)線程需要進(jìn)入臨界區(qū)時(shí),它會(huì)嘗試獲取互斥鎖的所有權(quán),如果互斥鎖已經(jīng)被其他線程占用,那么當(dāng)前線程就會(huì)進(jìn)入阻塞狀態(tài),直到互斥鎖被釋放為止。簡(jiǎn)單說(shuō)就是一塊區(qū)域只能被一個(gè)線程執(zhí)行。
當(dāng)一個(gè)線程獲取到互斥鎖的所有權(quán)后,它就可以進(jìn)入臨界區(qū)進(jìn)行操作,當(dāng)操作完成后,它需要釋放互斥鎖,讓其他線程有機(jī)會(huì)進(jìn)入臨界區(qū)。
下面是一個(gè)簡(jiǎn)單的互斥鎖的示例代碼,它演示了如何使用 std::mutex 類來(lái)保護(hù)臨界區(qū):
#include <iostream> #include <thread> #include <mutex> // 定義互斥鎖 std::mutex g_mutex; // 臨界區(qū)代碼 void critical_section(int thread_id) { // 加鎖 g_mutex.lock(); // 訪問(wèn)共享資源 std::cout << "Thread " << thread_id << " enter critical section." << std::endl; // 釋放鎖 std::this_thread::sleep_for(std::chrono::seconds(5)); g_mutex.unlock(); } int main() { // 創(chuàng)建兩個(gè)線程 std::thread t1(critical_section, 1); std::thread t2(critical_section, 2); // 等待兩個(gè)線程執(zhí)行完成 t1.join(); t2.join(); return 0; }
main中創(chuàng)建兩個(gè)線程去訪問(wèn)資源,但是其中一個(gè)需要等待另一個(gè)線程5s釋放后才能訪問(wèn),形成對(duì)資源的鎖定。
上面的例子使用的是std::mutex實(shí)現(xiàn)互斥鎖,需要注意這個(gè)互斥鎖的聲明需要相對(duì)的全局變量,也就是說(shuō)對(duì)于使用鎖的部分它必須是“全局的”。
二、遞歸互斥量(Recursive Mutex)
C++ 中的遞歸互斥量(Recursive Mutex)是一種特殊的互斥量,它可以被同一個(gè)線程多次鎖定,而不會(huì)發(fā)生死鎖。遞歸互斥量的實(shí)現(xiàn)原理是,在鎖定時(shí)維護(hù)一個(gè)鎖定計(jì)數(shù)器,每次解鎖時(shí)將計(jì)數(shù)器減一,只有當(dāng)計(jì)數(shù)器為 0 時(shí)才會(huì)釋放鎖。
以下是遞歸互斥量的示例代碼:
#include <iostream> #include <thread> #include <mutex> std::recursive_mutex mtx; void foo(int n) { mtx.lock(); std::cout << "Thread " << n << " locked the mutex." << std::endl; if (n > 1) { foo(n - 1); } std::cout << "Thread " << n << " unlocked the mutex." << std::endl; mtx.unlock(); } int main() { std::thread t1(foo, 3); std::thread t2(foo, 2); t1.join(); t2.join(); return 0; }
在上面的代碼中,我們定義了一個(gè)遞歸函數(shù) foo(),它接受一個(gè)整數(shù)參數(shù) n,表示當(dāng)前線程的編號(hào)。在函數(shù)中,我們首先使用遞歸互斥量 mtx 鎖定當(dāng)前線程,然后輸出一條帶有線程編號(hào)的信息,接著判斷如果 n 大于 1,則遞歸調(diào)用 foo() 函數(shù),并將參數(shù)減一。最后,我們輸出一條解鎖信息,并將遞歸互斥量解鎖。
在主函數(shù)中,我們創(chuàng)建了兩個(gè)線程 t1 和 t2,分別調(diào)用 foo() 函數(shù),并傳入不同的參數(shù)值。由于遞歸互斥量可以被同一個(gè)線程多次鎖定,因此在 t1 線程中對(duì) mtx 進(jìn)行了兩次鎖定,而在 t2 線程中只進(jìn)行了一次鎖定。
運(yùn)行結(jié)果:
可以看到,遞歸互斥量可以被同一個(gè)線程多次鎖定,并且在解鎖時(shí)必須對(duì)應(yīng)減少鎖定計(jì)數(shù)器。這種機(jī)制可以避免死鎖的發(fā)生,但也需要注意使用時(shí)的線程安全問(wèn)題。
三、讀寫鎖(Read-Write Lock)
讀寫鎖(Read-Write Lock)是一種特殊的互斥鎖,用于在多線程環(huán)境下對(duì)共享資源進(jìn)行讀寫操作。它允許多個(gè)線程同時(shí)讀取共享資源,但只允許一個(gè)線程寫入共享資源。讀寫鎖的使用可以提高并發(fā)性能,特別是當(dāng)讀操作比寫操作頻繁時(shí)。
在 C++ 中,讀寫鎖可以通過(guò) std::shared_mutex 類型來(lái)實(shí)現(xiàn)。下面是一個(gè)簡(jiǎn)單的示例代碼,演示了如何使用讀寫鎖來(lái)保護(hù)一個(gè)共享的整型變量:
#include <iostream> #include <thread> #include <chrono> #include <shared_mutex> std::shared_mutex rw_lock; // 讀寫鎖 int shared_var = 0; // 共享變量 // 寫線程函數(shù) void writer() { for (int i = 0; i < 10; ++i) { // 獨(dú)占寫鎖 std::unique_lock<std::shared_mutex> lock(rw_lock); // 寫共享變量 ++shared_var; std::cout << "Writer thread: write shared_var=" << shared_var << std::endl; // 等待一段時(shí)間 std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } // 讀線程函數(shù) void reader(int id) { for (int i = 0; i < 10; ++i) { // 共享讀鎖 std::shared_lock<std::shared_mutex> lock(rw_lock); // 讀共享變量 int value = shared_var; std::cout << "Reader thread " << id << ": read shared_var=" << value << std::endl; // 等待一段時(shí)間 std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } int main() { std::thread t1(writer); std::thread t2(reader, 1); std::thread t3(reader, 2); std::thread t4(reader, 3); t1.join(); t2.join(); t3.join(); t4.join(); return 0; }
在上面的代碼中,我們定義了一個(gè)共享變量 shared_var 和一個(gè)讀寫鎖 rw_lock。寫線程函數(shù) writer() 獨(dú)占寫鎖,對(duì) shared_var 進(jìn)行自增操作,并輸出當(dāng)前的值。讀線程函數(shù) reader() 共享讀鎖,讀取 shared_var 的值,并輸出當(dāng)前的值。所有的線程都會(huì)等待一段時(shí)間,以模擬實(shí)際的操作。
在主函數(shù)中,我們創(chuàng)建了一個(gè)寫線程和三個(gè)讀線程。由于讀寫鎖的特性,讀線程可以并發(fā)讀取共享變量,而寫線程會(huì)獨(dú)占寫鎖,只有在寫操作完成之后,讀線程才能再次讀取共享變量。因此,輸出結(jié)果中讀線程的順序可能會(huì)有所不同,但是寫線程的操作一定是順序執(zhí)行的。
注意,這里使用 std::unique_lockstd::shared_mutex 類型的對(duì)象來(lái)獲取獨(dú)占寫鎖,使用 std::shared_lockstd::shared_mutex 類型的對(duì)象來(lái)獲取共享讀鎖。這些鎖對(duì)象會(huì)在作用域結(jié)束時(shí)自動(dòng)解鎖,避免了手動(dòng)解鎖的問(wèn)題。
四、條件變量(Condition Variable)
條件變量(Condition Variable)是一種線程間同步機(jī)制,用于在某些特定條件下阻塞或喚醒線程。在 C++ 中,條件變量是通過(guò) std::condition_variable 類來(lái)實(shí)現(xiàn)的。
下面是一個(gè)使用條件變量的示例代碼,其中有兩個(gè)線程,一個(gè)線程不停地生產(chǎn)數(shù)據(jù),另一個(gè)線程則等待數(shù)據(jù),當(dāng)有數(shù)據(jù)可用時(shí),將數(shù)據(jù)進(jìn)行消費(fèi)。
#include <iostream> #include <thread> #include <chrono> #include <queue> #include <mutex> #include <condition_variable> std::queue<int> data_queue; // 數(shù)據(jù)隊(duì)列 std::mutex data_mutex; // 互斥鎖 std::condition_variable data_cond; // 條件變量 // 生產(chǎn)數(shù)據(jù)函數(shù) void producer() { for (int i = 1; i <= 10; ++i) { // 生產(chǎn)數(shù)據(jù) std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::unique_lock<std::mutex> lock(data_mutex); data_queue.push(i); std::cout << "Producer thread: produce data " << i << std::endl; // 喚醒消費(fèi)線程 data_cond.notify_one(); } } // 消費(fèi)數(shù)據(jù)函數(shù) void consumer() { while (true) { // 等待數(shù)據(jù) std::unique_lock<std::mutex> lock(data_mutex); data_cond.wait(lock, [] { return !data_queue.empty(); }); // 消費(fèi)數(shù)據(jù) int data = data_queue.front(); data_queue.pop(); std::cout << "Consumer thread: consume data " << data << std::endl; // 檢查是否結(jié)束 if (data == 10) { break; } } } int main() { std::thread t1(producer); std::thread t2(consumer); t1.join(); t2.join(); return 0; }
在上面的代碼中,我們定義了一個(gè)數(shù)據(jù)隊(duì)列 data_queue 和一個(gè)互斥鎖 data_mutex,同時(shí)定義了一個(gè)條件變量 data_cond。生產(chǎn)數(shù)據(jù)的函數(shù) producer() 不停地往隊(duì)列中添加數(shù)據(jù),每次添加完數(shù)據(jù)之后,通過(guò)調(diào)用 data_cond.notify_one() 喚醒等待的消費(fèi)線程。消費(fèi)數(shù)據(jù)的函數(shù) consumer() 通過(guò)調(diào)用 data_cond.wait(lock, [] { return !data_queue.empty(); }) 來(lái)等待數(shù)據(jù),當(dāng)隊(duì)列中有數(shù)據(jù)時(shí),將數(shù)據(jù)從隊(duì)列中取出并消費(fèi),如果取出的數(shù)據(jù)是最后一個(gè),則退出循環(huán)。
在主函數(shù)中,我們創(chuàng)建了一個(gè)生產(chǎn)線程和一個(gè)消費(fèi)線程。生產(chǎn)線程生產(chǎn) 10 個(gè)數(shù)據(jù),消費(fèi)線程從隊(duì)列中消費(fèi)數(shù)據(jù),直到消費(fèi)到最后一個(gè)數(shù)據(jù)為止。
注意,這里使用了 std::unique_lockstd::mutex 類型的對(duì)象來(lái)獲取互斥鎖,并使用 lambda 表達(dá)式 [] { return !data_queue.empty(); } 來(lái)判斷條件是否滿足。在調(diào)用 wait() 函數(shù)時(shí),當(dāng)前線程會(huì)阻塞,直到條件變量被其他線程喚醒或超時(shí)。當(dāng) wait() 函數(shù)返回時(shí),當(dāng)前線程會(huì)重新獲取互斥。
簡(jiǎn)單一些的例子:
#include <iostream> #include <thread> #include <chrono> #include <mutex> #include <condition_variable> bool ready = false; // 條件變量 std::mutex data_mutex; // 互斥鎖 std::condition_variable data_cond; // 條件變量 void do_something() { // 模擬工作 std::this_thread::sleep_for(std::chrono::milliseconds(500)); } void waiting_thread() { // 等待條件變量 std::unique_lock<std::mutex> lock(data_mutex); data_cond.wait(lock, [] { return ready; }); // 條件滿足后輸出一句話 std::cout << "Condition satisfied, waiting thread resumes." << std::endl; do_something(); } int main() { std::thread t1(waiting_thread); // 模擬條件滿足后的操作 std::this_thread::sleep_for(std::chrono::milliseconds(1000)); { std::lock_guard<std::mutex> lock(data_mutex); ready = true; data_cond.notify_one(); } t1.join(); return 0; }
在上面的代碼中,我們定義了一個(gè)條件變量 ready 和一個(gè)互斥鎖 data_mutex,同時(shí)定義了一個(gè)條件變量 data_cond。等待條件變量的函數(shù) waiting_thread() 首先獲取互斥鎖,然后通過(guò)調(diào)用 data_cond.wait(lock, [] { return ready; }) 等待條件變量,當(dāng) ready 為 true 時(shí),線程會(huì)被喚醒,輸出一句話,并模擬一些工作的操作。在主函數(shù)中,我們創(chuàng)建了一個(gè)等待條件變量的線程 t1,然后模擬條件滿足后的操作,即將 ready 設(shè)置為 true,然后通過(guò)調(diào)用 data_cond.notify_one() 喚醒等待的線程。
五、總結(jié)
互斥鎖保證了計(jì)算機(jī)資源訪問(wèn)的安全,互斥鎖的不當(dāng)使用同時(shí)也加大了程序阻塞的風(fēng)險(xiǎn)。
提前祝大家五一前工作生活學(xué)習(xí)一切順利。
到此這篇關(guān)于C++ 互斥鎖原理以及實(shí)際使用介紹的文章就介紹到這了,更多相關(guān)C++ 互斥鎖原理及使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實(shí)現(xiàn)商店倉(cāng)庫(kù)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)商店倉(cāng)庫(kù)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03C++動(dòng)態(tài)分配和撤銷內(nèi)存以及結(jié)構(gòu)體類型作為函數(shù)參數(shù)
這篇文章主要介紹了C++動(dòng)態(tài)分配和撤銷內(nèi)存以及結(jié)構(gòu)體類型作為函數(shù)參數(shù),是C++入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09詳解C++的JSON靜態(tài)鏈接庫(kù)JsonCpp的使用方法
這篇文章主要介紹了C++的JSON靜態(tài)鏈接庫(kù)JsonCpp的使用方法,演示了使用JsonCpp生成和解析JSON的方法,以及C++通過(guò)JSON方式的socket通信示例,需要的朋友可以參考下2016-03-03StretchBlt函數(shù)和BitBlt函數(shù)用法案例詳解
這篇文章主要介紹了StretchBlt函數(shù)和BitBlt函數(shù)用法案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08C語(yǔ)言動(dòng)態(tài)內(nèi)存管理的原理及實(shí)現(xiàn)方法
C語(yǔ)言動(dòng)態(tài)內(nèi)存管理的原理是通過(guò) malloc() 函數(shù)申請(qǐng)一塊連續(xù)的內(nèi)存空間,并返回其地址,通過(guò) free() 函數(shù)釋放該內(nèi)存空間。實(shí)現(xiàn)方法是通過(guò)在程序運(yùn)行時(shí)動(dòng)態(tài)地管理內(nèi)存,即在需要內(nèi)存時(shí)申請(qǐng),不需要時(shí)釋放,避免了靜態(tài)內(nèi)存分配的浪費(fèi)和不足2023-04-04c語(yǔ)言動(dòng)態(tài)內(nèi)存分配知識(shí)點(diǎn)及實(shí)例
在本篇文章里小編給大家整理的是關(guān)于c語(yǔ)言動(dòng)態(tài)內(nèi)存分配知識(shí)點(diǎn)及實(shí)例,需要的朋友們可以學(xué)習(xí)下。2020-03-03用C++實(shí)現(xiàn)strcpy(),返回一個(gè)char*類型的深入分析
本篇文章是對(duì)用C++實(shí)現(xiàn)strcpy(),返回一個(gè)char*類型進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05