英偉達(dá)C++一面,Mutex底層原理是什么?
mutex 即互斥鎖,是多線程編程里用于保障數(shù)據(jù)一致性、避免競態(tài)條件的關(guān)鍵同步工具。在多線程環(huán)境下,多個(gè)線程可能同時(shí)嘗試訪問共享資源,這就可能引發(fā)數(shù)據(jù)沖突等問題,而 mutex 能確保同一時(shí)刻僅有一個(gè)線程可以訪問臨界區(qū),即訪問共享資源的代碼段。
從應(yīng)用場景來看,像多線程訪問共享內(nèi)存、操作共享文件等場景,mutex 都發(fā)揮著重要作用。比如多個(gè)線程對(duì)同一個(gè)計(jì)數(shù)器進(jìn)行增減操作時(shí),若沒有 mutex,最終結(jié)果可能與預(yù)期不符。理解 mutex 底層原理,不僅能幫助開發(fā)者寫出更高效、更健壯的多線程代碼,排查多線程相關(guān)的問題,還能讓開發(fā)者在面對(duì)不同場景時(shí),合理選擇同步策略。接下來,我們深入剖析 mutex 的底層原理 。
Part1.什么是mutex ?
1.1多線程編程中的同步問題
在當(dāng)今的軟件開發(fā)領(lǐng)域,多線程編程已成為提升程序性能和響應(yīng)速度的關(guān)鍵技術(shù)。隨著計(jì)算機(jī)硬件的不斷發(fā)展,多核處理器已成為主流,這使得程序能夠同時(shí)執(zhí)行多個(gè)線程,充分利用硬件資源 。然而,多線程編程也帶來了一系列復(fù)雜的問題,其中同步問題尤為突出。
想象一下,有多個(gè)線程同時(shí)訪問和修改同一個(gè)共享資源,比如一個(gè)銀行賬戶的余額。當(dāng)一個(gè)線程讀取賬戶余額后,還沒來得及更新余額,另一個(gè)線程也讀取了相同的余額并進(jìn)行了修改,這就導(dǎo)致最終的余額可能是錯(cuò)誤的。這種數(shù)據(jù)不一致的情況,就是多線程同步問題的典型表現(xiàn),專業(yè)術(shù)語稱之為 “競態(tài)條件”。
再比如,在一個(gè)網(wǎng)絡(luò)服務(wù)器程序中,多個(gè)線程可能同時(shí)處理客戶端的請(qǐng)求,如果沒有合適的同步機(jī)制,可能會(huì)導(dǎo)致數(shù)據(jù)混亂,甚至服務(wù)器崩潰。這些問題不僅影響程序的正確性,還可能導(dǎo)致嚴(yán)重的后果,如金融交易中的資金錯(cuò)誤、系統(tǒng)故障等 。因此,解決多線程同步問題迫在眉睫,它是保證程序穩(wěn)定運(yùn)行的關(guān)鍵。
1.2mutex 的定義與作用
為了解決多線程同步問題,mutex(互斥鎖)應(yīng)運(yùn)而生。mutex,即 mutual exclusion 的縮寫,意為互斥 。它就像是一個(gè)關(guān)卡守衛(wèi),確保同一時(shí)刻只有一個(gè)線程能夠進(jìn)入被保護(hù)的區(qū)域,這個(gè)區(qū)域被稱為臨界區(qū)。
臨界區(qū)是程序中訪問共享資源的代碼片段,比如前面提到的修改銀行賬戶余額的代碼、處理網(wǎng)絡(luò)請(qǐng)求的關(guān)鍵代碼等。當(dāng)一個(gè)線程進(jìn)入臨界區(qū)前,必須先獲取 mutex 鎖。如果鎖是空閑的,線程就可以獲取鎖并進(jìn)入臨界區(qū),此時(shí)其他線程若想進(jìn)入,就會(huì)被阻塞,直到持有鎖的線程離開臨界區(qū)并釋放鎖 。
例如,在一個(gè)多線程的文件讀寫程序中,文件就是共享資源,對(duì)文件進(jìn)行讀寫的代碼部分就是臨界區(qū)。通過 mutex 鎖,就能保證同一時(shí)間只有一個(gè)線程可以對(duì)文件進(jìn)行讀寫操作,避免了數(shù)據(jù)混亂和文件損壞的風(fēng)險(xiǎn)。mutex 的這種機(jī)制,就像是給共享資源加上了一把鎖,只有拿到鑰匙(獲取鎖)的線程才能訪問,從而有效地保護(hù)了共享資源,確保了多線程環(huán)境下程序的正確性和穩(wěn)定性 。
C++11中mutex相關(guān)的類都在<mutex>頭文件中。共四種互斥類:

與std::thread一樣,mutex相關(guān)類不支持拷貝構(gòu)造、不支持賦值。同時(shí)mutex類也不支持move語義(move構(gòu)造、move賦值)。不用擔(dān)心會(huì)誤用這些操作,真要這么做了的話,編譯器會(huì)阻止你的。
Part2.mutex核心成員函數(shù)
mutex的標(biāo)準(zhǔn)操作,四個(gè)mutex類都支持這些操作,但是不同類在行為上有些微的差異。
2.1 lock函數(shù)
鎖住互斥量。調(diào)用lock時(shí)有三種情況:
- 如果互斥量沒有被鎖住,則調(diào)用線程將該mutex鎖住,直到調(diào)用線程調(diào)用unlock釋放。
 - 如果mutex已被其它線程lock,則調(diào)用線程將被阻塞,直到其它線程unlock該mutex。
 - 如果當(dāng)前mutex已經(jīng)被調(diào)用者線程鎖住,則std::mutex死鎖,而recursive系列則成功返回。
 
2.2 try_lock函數(shù)
嘗試鎖住mutex,調(diào)用該函數(shù)同樣也有三種情況:
- 如果互斥量沒有被鎖住,則調(diào)用線程將該mutex鎖住(返回true),直到調(diào)用線程調(diào)用unlock釋放。
 - 如果mutex已被其它線程lock,則調(diào)用線程將失敗,并返回false。
 - 如果當(dāng)前mutex已經(jīng)被調(diào)用者線程鎖住,則std::mutex死鎖,而recursive系列則成功返回true。
 
2.3 unlock函數(shù)
解鎖mutex,釋放對(duì)mutex的所有權(quán)。值得一提的時(shí),對(duì)于recursive系列mutex,unlock次數(shù)需要與lock次數(shù)相同才可以完全解鎖。下面給出一個(gè)mutex小例子:
#include <iostream>
#include <thread>
#include <mutex>
void inc(std::mutex &mutex, int loop, int &counter) {
    for (int i = 0; i < loop; i++) {
        mutex.lock();
        ++counter;
        mutex.unlock();
    }
}
int main() {
    std::thread threads[5];
    std::mutex mutex;
    int counter = 0;
    for (std::thread &thr: threads) {
        thr = std::thread(inc, std::ref(mutex), 1000, std::ref(counter));
    }
    for (std::thread &thr: threads) {
        thr.join();
    }
    // 輸出:5000,如果inc中調(diào)用的是try_lock,則此處可能會(huì)<5000
    std::cout << counter << std::endl;
    return 0;
}
//: g++ -std=c++11 main.cppPart3.mutex底層原理深度解析
3.1基于原子操作實(shí)現(xiàn)
mutex的底層實(shí)現(xiàn),離不開原子操作這一關(guān)鍵技術(shù)。原子操作,就像是編程世界里的 “獨(dú)行俠”,它是不可被中斷的一個(gè)或一系列操作 。在多線程環(huán)境中,原子操作確保了數(shù)據(jù)訪問和修改的完整性,不會(huì)被線程調(diào)度機(jī)制打斷 。以常見的比較并交換(CAS,Compare And Swap)操作為例,它是實(shí)現(xiàn) mutex 的重要原子操作之一。CAS 操作包含三個(gè)參數(shù):內(nèi)存位置、預(yù)期值和新值 。當(dāng)執(zhí)行 CAS 操作時(shí),它會(huì)先檢查內(nèi)存位置的值是否與預(yù)期值相等,如果相等,就將內(nèi)存位置的值更新為新值,整個(gè)過程是原子性的,不會(huì)被其他線程干擾 。
在 mutex 的實(shí)現(xiàn)中,CAS 操作被用于判斷和修改鎖的狀態(tài)。假設(shè) mutex 的鎖狀態(tài)用一個(gè)變量表示,0 代表未鎖定,1 代表已鎖定 。當(dāng)一個(gè)線程嘗試獲取鎖時(shí),會(huì)使用 CAS 操作將鎖狀態(tài)從 0 嘗試更新為 1。如果當(dāng)前鎖狀態(tài)確實(shí)是 0,更新成功,線程就獲取到了鎖;如果鎖狀態(tài)已經(jīng)是 1,更新失敗,線程就知道鎖已被其他線程持有,需要等待 。這種基于原子操作的方式,保證了對(duì)鎖狀態(tài)的修改是原子性的,避免了多個(gè)線程同時(shí)修改鎖狀態(tài)導(dǎo)致的競爭條件 ,就像多個(gè)玩家搶奪一個(gè)寶物,只有一個(gè)玩家能成功拿到,其他人只能等待。
3.2操作系統(tǒng)層面支持
除了原子操作,操作系統(tǒng)也在 mutex 的實(shí)現(xiàn)中扮演著不可或缺的角色 。操作系統(tǒng)為 mutex 提供了線程阻塞與喚醒機(jī)制,這是確保多線程環(huán)境下資源有序訪問的關(guān)鍵。
當(dāng)一個(gè)線程嘗試獲取已被鎖定的 mutex 時(shí),操作系統(tǒng)會(huì)將該線程阻塞,使其進(jìn)入睡眠狀態(tài),讓出 CPU 資源,避免無效的 CPU 占用 。而當(dāng)持有鎖的線程釋放鎖時(shí),操作系統(tǒng)會(huì)從等待隊(duì)列中喚醒一個(gè)或多個(gè)等待該鎖的線程,讓它們有機(jī)會(huì)競爭獲取鎖 。
在 Linux 系統(tǒng)中,futex(快速用戶空間互斥體)就是與 mutex 緊密相關(guān)的底層機(jī)制 。futex 的設(shè)計(jì)理念非常巧妙,它結(jié)合了用戶空間和內(nèi)核空間的優(yōu)勢 。當(dāng)線程嘗試獲取鎖時(shí),首先會(huì)在用戶空間進(jìn)行快速檢查,如果鎖可用,直接在用戶空間獲取鎖,避免進(jìn)入內(nèi)核空間,大大提高了效率 。
只有當(dāng)鎖被占用時(shí),才會(huì)通過系統(tǒng)調(diào)用進(jìn)入內(nèi)核空間,將線程阻塞并放入等待隊(duì)列 。這種機(jī)制減少了不必要的內(nèi)核態(tài)與用戶態(tài)切換,降低了系統(tǒng)開銷,就像一個(gè)智能的門衛(wèi),能快速判斷是否讓訪客進(jìn)入,避免了繁瑣的手續(xù)。
3.3具體實(shí)現(xiàn)細(xì)節(jié)分析(以 glibc 為例)
為了更深入地理解 mutex 的底層實(shí)現(xiàn),我們以 glibc 中的 mutex 實(shí)現(xiàn)為例進(jìn)行剖析 。在 glibc 中,pthread_mutex_t 是表示互斥鎖的數(shù)據(jù)結(jié)構(gòu),它包含了幾個(gè)重要的字段,每個(gè)字段都有著獨(dú)特的作用 。
__lock 字段用于表示鎖的狀態(tài),0 表示未鎖定,非 0 表示已鎖定 。__owner 字段記錄當(dāng)前持有鎖的線程 ID,這樣可以判斷鎖是否被當(dāng)前線程持有,避免重復(fù)加鎖導(dǎo)致死鎖 。__kind 字段則定義了互斥鎖的類型,不同類型的鎖有著不同的行為,比如普通鎖、遞歸鎖等 。
再看 pthread_mutex_lock 函數(shù)的實(shí)現(xiàn)邏輯,當(dāng)一個(gè)線程調(diào)用 pthread_mutex_lock 嘗試獲取鎖時(shí),首先會(huì)在用戶層進(jìn)行快速檢查,查看__lock 字段的值 。如果__lock 為 0,說明鎖可用,線程會(huì)使用原子操作將__lock 設(shè)置為非 0,從而獲取鎖 。如果__lock 非 0,說明鎖已被其他線程持有,線程會(huì)根據(jù)__kind 字段判斷鎖的類型 。對(duì)于普通鎖,線程會(huì)進(jìn)入內(nèi)核層,通過 futex 機(jī)制將自己阻塞,等待鎖的釋放 。而對(duì)于遞歸鎖,如果當(dāng)前線程是鎖的持有者,線程可以再次獲取鎖,同時(shí)增加鎖的持有計(jì)數(shù) 。
pthread_mutex_unlock 函數(shù)的實(shí)現(xiàn)同樣關(guān)鍵,當(dāng)線程調(diào)用 pthread_mutex_unlock 釋放鎖時(shí),會(huì)先在用戶層將__lock 字段設(shè)置為 0,然后根據(jù)__kind 字段和__owner 字段進(jìn)行相應(yīng)操作 。如果有其他線程在等待鎖,會(huì)通過 futex 機(jī)制喚醒等待隊(duì)列中的一個(gè)線程 。對(duì)于遞歸鎖,會(huì)減少鎖的持有計(jì)數(shù),只有當(dāng)計(jì)數(shù)為 0 時(shí),才真正釋放鎖 。這些實(shí)現(xiàn)細(xì)節(jié),就像是精密儀器里的齒輪,相互配合,確保了 mutex 在多線程環(huán)境下的穩(wěn)定運(yùn)行 。
Part4.mutex實(shí)現(xiàn)方式
4.1直接操作mutex,即直接調(diào)用mutex 的lock / unlock 函數(shù)
此例順帶使用了 boost::thread_group 來創(chuàng)建一組線程。
#include <iostream>
#include <mutex>
#include <vector>
#include <boost/thread/thread.hpp>
std::mutex g_mutex; // 全局互斥鎖
int g_counter = 0;   // 共享資源
void thread_func(int id) {
    for (int i = 0; i < 5; ++i) {
        // 直接操作 mutex(不推薦!應(yīng)用 std::lock_guard)
        g_mutex.lock(); // 手動(dòng)加鎖
        // 臨界區(qū)開始
        int current = ++g_counter;
        std::cout << "Thread " << id << " incremented counter to: " << current << std::endl;
        // 臨界區(qū)結(jié)束
        g_mutex.unlock(); // 手動(dòng)解鎖
        boost::this_thread::sleep(boost::posix_time::milliseconds(100));
    }
}
int main() {
    boost::thread_group threads;
    // 創(chuàng)建4個(gè)線程加入線程組
    for (int i = 0; i < 4; ++i) {
        threads.create_thread(boost::bind(&thread_func, i + 1));
    }
    threads.join_all(); // 等待所有線程結(jié)束
    std::cout << "Final counter value: " << g_counter << std::endl;
    return 0;
}- 直接操作 mutex:顯式調(diào)用 lock()/unlock(),但實(shí)際開發(fā)中更推薦使用 std::lock_guard(RAII機(jī)制避免忘記解鎖)。
 - boost::thread_group:用于管理一組線程,通過 create_thread()添加線程,join_all()等待所有線程完成。
 
輸出示例:
Thread 1 incremented counter to: 1
Thread 2 incremented counter to: 2
Thread - Final counter value:20(最終結(jié)果正確)??注意事項(xiàng):
- 必須配對(duì)調(diào)用 lock/unlock:否則會(huì)導(dǎo)致死鎖或未定義行為。
 - 異常安全風(fēng)險(xiǎn):若臨界區(qū)代碼拋出異常,可能無法執(zhí)行 unlock(),此時(shí)應(yīng)改用 std::lock_guard。
 
(推薦)改進(jìn)版本(使用 RAII):
void thread_func(int id) {
    for (int i = ; i <5;++i){
        std::
            guard<std::
                tex> lock(g_mutex); 
        當(dāng)前值=++g_counter;
       ::cout<<"Thread "<<id<<": "<<current<<'\n';
     } 
}直接操作 mutex,即直接調(diào)用 mutex 的 lock / unlock 函數(shù),此例順帶使用了 boost::thread_group 來創(chuàng)建一組線程。
4.2使用lock_guard 自動(dòng)加鎖、解鎖。原理是 RAII,和智能指針類似
C++11 標(biāo)準(zhǔn)版(無需 Boost)
#include <iostream>
#include <mutex>
#include <vector>
#include <thread>
std::mutex g_mutex; // 全局互斥鎖
int g_counter = 0;   // 共享資源
void thread_func(int id) {
    for (int i = 0; i < 5; ++i) {
        // RAII:構(gòu)造時(shí)自動(dòng)加鎖,析構(gòu)時(shí)自動(dòng)解鎖
        std::lock_guard<std::mutex> lock(g_mutex);
        // 臨界區(qū)操作
        int current = ++g_counter;
        std::cout << "Thread " << id << " incremented counter to: " << current << std::endl;
        // lock_guard 析構(gòu)時(shí)會(huì)自動(dòng)調(diào)用 g_mutex.unlock()
    }
}
int main() {
    std::vector<std::thread> threads;
    // 創(chuàng)建4個(gè)線程
    for (int i = 0; i < 4; ++i) {
        threads.emplace_back(thread_func, i + 1);
    }
    // 等待所有線程完成
    for (auto& t : threads) {
        t.join();
    }
    std::cout << "Final counter value: " << g_counter << std::endl;
    return 0;
}(1)RAII(Resource Acquisition Is Initialization)
- std::lock_guard 在構(gòu)造函數(shù)中調(diào)用 mutex.lock(),在析構(gòu)函數(shù)中調(diào)用 mutex.unlock()。
 - 即使臨界區(qū)代碼拋出異常,也能保證鎖被釋放。
 
(2)對(duì)比手動(dòng) lock/unlock
// 手動(dòng)管理(易出錯(cuò))
g_mutex.lock();
/* ...操作... */
g_mutex.unlock(); // 若中間拋出異常,unlock()可能不被執(zhí)行!
// RAII(推薦)
{
    std::lock_guard<std::mutex> lock(g_mutex); // 構(gòu)造時(shí)加鎖
    /* ...操作... */
} // lock_guard析構(gòu)時(shí)自動(dòng)解鎖(3)輸出結(jié)果示例
Thread - Final counter value:20(正確同步)擴(kuò)展:C++17 的 std::scoped_lock
若需同時(shí)鎖定多個(gè)互斥量,可使用更現(xiàn)代的 scoped_lock:
std::mutex mtx1, mtx2;
{
    std::scoped_lock lock(mtx1, mtx2); // C++17起支持,自動(dòng)解決多鎖死鎖問題
    /* ...操作... */
}編譯與運(yùn)行
編譯命令(需支持 C++11):
g++ -std=c++11 -pthread example.cpp -o example && ./example此方案是工業(yè)級(jí)代碼中的標(biāo)準(zhǔn)做法,徹底避免因忘記解鎖或異常導(dǎo)致的死鎖問題。
4.3使用 unique_lock 自動(dòng)加鎖、解鎖
unique_lock 與 lock_guard 原理相同,但是提供了更多功能(比如可以結(jié)合條件變量使用)。
注意:mutex::scoped_lock 其實(shí)就是 unique_lock<mutex> 的 typedef。
C++11 標(biāo)準(zhǔn)版(含條件變量協(xié)作)
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
std::mutex g_mutex;
std::condition_variable g_cv;
bool g_ready = false; // 共享?xiàng)l件狀態(tài)
int g_data = 0;       // 共享數(shù)據(jù)
// 生產(chǎn)者線程:準(zhǔn)備數(shù)據(jù)并通知消費(fèi)者
void producer() {
    {
        std::unique_lock<std::mutex> lock(g_mutex);
        // 模擬耗時(shí)操作
        std::this_thread::sleep_for(std::chrono::seconds(1));
        g_data = 42;                  // 生產(chǎn)數(shù)據(jù)
        g_ready = true;               // 標(biāo)記數(shù)據(jù)就緒
    }                                 // unique_lock析構(gòu)時(shí)自動(dòng)解鎖
    g_cv.notify_one();                // 通知等待的消費(fèi)者線程
}
// 消費(fèi)者線程:等待數(shù)據(jù)就緒后消費(fèi)
void consumer() {
    std::unique_lock<std::mutex> lock(g_mutex);
    //  unique_lock特有功能:與條件變量配合時(shí)可臨時(shí)解鎖
    g_cv.wait(lock, [] { return g_ready; });
    std::cout << "Consumed data: " << g_data << std::endl;
}
int main() {
    std::thread producer_thread(producer);
    std::thread consumer_thread(consumer);
    producer_thread.join();
    consumer_thread.join();
    return 0;
}(1)與 lock_guard 的核心區(qū)別
特性  | std::lock_guard  | std::unique_lock  | 
鎖管理靈活性  | 構(gòu)造即鎖定,不可手動(dòng)控制  | 可延遲鎖定(defer_lock)或提前釋放(unlock())  | 
支持條件變量  | (wait()需配合unique_lock)  | |
性能開銷  | 更低  | 略高(因需維護(hù)鎖狀態(tài))  | 
(2)延遲鎖定示例
std::mutex mtx;
void deferred_lock_example() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 聲明但不立即加鎖
    /* ...其他無需同步的操作... */
    lock.lock();              // 手動(dòng)加鎖(靈活控制臨界區(qū)范圍)
    /* ...臨界區(qū)操作... */
    lock.unlock();            // 可手動(dòng)提前解鎖(非必須)
}(3)輸出結(jié)果示例
Consumed data:42(生產(chǎn)者-消費(fèi)者正確同步)何時(shí)選擇 unique_lock?
- 需要配合條件變量(如生產(chǎn)者-消費(fèi)者模型)
 - 需要靈活控制加鎖時(shí)機(jī)(如先預(yù)判再?zèng)Q定是否進(jìn)入臨界區(qū))
 - 需要轉(zhuǎn)移鎖所有權(quán)(可通過移動(dòng)語義將鎖傳遞給其他作用域)
 
(4)編譯與運(yùn)行
編譯命令:
g++ -std=c++11 -pthread example.cpp -o example && ./example通過 unique_lock,開發(fā)者可以在保證RAII安全的前提下,獲得更精細(xì)的鎖控制能力,尤其適合復(fù)雜同步場景。
4.4為輸出流使用單獨(dú)的 mutex
針對(duì)輸出流(如 std::cout)使用獨(dú)立 mutex 的完整可運(yùn)行代碼示例,確保多線程環(huán)境下日志/輸出的原子性和順序性。
方案1:全局輸出鎖(基礎(chǔ)版)
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
// 全局互斥鎖,專用于保護(hù) std::cout
std::mutex g_cout_mutex;
void safe_print(const std::string& message) {
    std::lock_guard<std::mutex> lock(g_cout_mutex); // 自動(dòng)加鎖/解鎖
    std::cout << "[Thread " << std::this_thread::get_id() << "] " << message << std::endl;
}
void worker(int id) {
    safe_print("Start working...");
    // 模擬工作耗時(shí)
    std::this_thread::sleep_for(std::chrono::milliseconds(100)); 
    safe_print("Job " + std::to_string(id) + " completed.");
}
int main() {
    const int kNumThreads = 5;
    std::vector<std::thread> threads;
    for (int i = 0; i < kNumThreads; ++i) {
        threads.emplace_back(worker, i);
    }
    for (auto& t : threads) {
        t.join();
    }
    return 0;
}- 專用互斥鎖:g_cout_mutex 僅保護(hù) std::cout,避免與其他業(yè)務(wù)邏輯鎖競爭。
 - RAII管理:lock_guard 確保異常安全。
 - 線程標(biāo)識(shí):輸出中包含線程ID便于調(diào)試。
 
方案2:封裝為線程安全的Logger類(推薦)
#include <mutex>
#include <fstream>
#include <string>
class Logger {
public:
    Logger() {
        // 初始化日志文件
        log_file_.open("log.txt", std::ios::out);
    }
    ~Logger() {
        if (log_file_.is_open()) {
            log_file_.close();
        }
    }
    void LogMessage(const std::string& message) {
        // 使用lock_guard自動(dòng)管理互斥鎖
        std::lock_guard<std::mutex> lock(mutex_);
        log_file_ << message << std::endl;
    }
private:
    std::ofstream log_file_;
    std::mutex mutex_;
};Logger類包含一個(gè)用于寫入日志的文件流log_file_和一個(gè)互斥鎖mutex_。LogMessage函數(shù)用于記錄日志,通過std::lock_guard<std::mutex>來鎖定互斥鎖,確保同一時(shí)間只有一個(gè)線程能訪問日志文件進(jìn)行寫入操作,從而實(shí)現(xiàn)線程安全。還可以利用條件變量和隊(duì)列實(shí)現(xiàn)更復(fù)雜的線程安全日志類,如下所示:
#include <mutex>
#include <condition_variable>
#include <queue>
#include <iostream>
class Logger {
public:
    void WaitAndLog() {
        std::unique_lock<std::mutex> lock(mutex_);
        while (log_queue_.empty()) {
            // 等待條件變量通知
            cond_var_.wait(lock);
        }
        std::string message = log_queue_.front();
        log_queue_.pop();
        lock.unlock();
        std::cout << "Log Message: " << message << std::endl;
    }
    void AddLogMessage(const std::string& message) {
        std::unique_lock<std::mutex> lock(mutex_);
        log_queue_.push(message);
        lock.unlock();
        // 通知一個(gè)等待的線程
        cond_var_.notify_one();
    }
private:
    std::queue<std::string> log_queue_;
    std::mutex mutex_;
    std::condition_variable cond_var_;
};此版本的Logger類采用生產(chǎn)者 - 消費(fèi)者模式,AddLogMessage函數(shù)作為生產(chǎn)者將日志消息放入隊(duì)列,WaitAndLog函數(shù)作為消費(fèi)者從隊(duì)列中取出消息并打印,通過條件變量cond_var_和互斥鎖mutex_實(shí)現(xiàn)線程間的同步和互斥,保證線程安全。
方案3:C++20的 std:osyncstream(現(xiàn)代替代)
若編譯器支持C++20,可直接使用標(biāo)準(zhǔn)庫提供的同步流:
#include <syncstream>
#include <iostream>
void worker(int id){
   // 自動(dòng)同步輸出(底層自帶互斥鎖)
   std:osyncstream(std:cout)<<"[Thread "<<std:this_thread:.get_id<<"] "
                           <<"Hello from task"<<id<<'\n';  
}優(yōu)點(diǎn):無需手動(dòng)管理鎖,語法簡潔。缺點(diǎn):兼容性要求高。
性能注意事項(xiàng)
- 避免高頻小日志:頻繁加鎖會(huì)導(dǎo)致性能下降,建議批量合并日志。
 - 禁用 std:endl:它隱含 flush() 操作,改用 '\n'。
 - 異步日志庫:生產(chǎn)環(huán)境推薦使用 spdlog等專業(yè)庫。
 
編譯與運(yùn)行
# C++11版本編譯命令
g++ -std=c++11 -pthread safe_cout.cpp -o safe_cout && ./safe_cout
# C++20版本編譯命令(需支持)
g++ -std=c++20 -pthread syncstream.cpp -o syncstream && ./syncstream通過獨(dú)立互斥鎖或現(xiàn)代化同步工具,可徹底解決多線程輸出的亂序問題。
Part5.mutex與其他同步機(jī)制對(duì)比
5.1 mutex 與自旋鎖對(duì)比
自旋鎖是一種特殊的同步機(jī)制,它與 mutex 有著顯著的區(qū)別 。當(dāng)一個(gè)線程嘗試獲取自旋鎖時(shí),如果鎖已經(jīng)被其他線程持有,它不會(huì)像 mutex 那樣將線程阻塞,而是進(jìn)入一個(gè)循環(huán),不斷地檢查鎖的狀態(tài),這個(gè)過程被稱為 “自旋” 。就好比一個(gè)人去敲門,發(fā)現(xiàn)門被鎖上了,他不離開,而是一直在門口敲門,直到門被打開 。
自旋鎖的優(yōu)點(diǎn)在于響應(yīng)速度快,因?yàn)樗苊饬司€程阻塞和喚醒所帶來的開銷 。在鎖被占用時(shí)間非常短的情況下,自旋等待所花費(fèi)的時(shí)間遠(yuǎn)遠(yuǎn)小于線程阻塞的開銷,此時(shí)使用自旋鎖可以提高程序的運(yùn)行效率 。比如,在多核 CPU 環(huán)境中,當(dāng)一個(gè)線程在某個(gè)核心上自旋時(shí),不會(huì)影響其他核心上線程的正常工作,自旋鎖能發(fā)揮出較好的性能 。
然而,自旋鎖也有明顯的缺點(diǎn) 。由于線程在自旋時(shí)會(huì)一直占用 CPU 進(jìn)行 “空轉(zhuǎn)”,不斷地檢查鎖的狀態(tài),這會(huì)浪費(fèi)大量的 CPU 資源 。如果鎖被占用的時(shí)間很長,自旋的線程會(huì)持續(xù)占用 CPU,不僅自身無法高效工作,還可能導(dǎo)致其他線程沒有足夠的 CPU 時(shí)間來執(zhí)行任務(wù),甚至出現(xiàn) “餓死” 的情況 。
相比之下,mutex 在鎖被占用時(shí),會(huì)將線程阻塞,使其進(jìn)入睡眠狀態(tài),讓出 CPU 資源,避免了無效的 CPU 占用 。當(dāng)鎖的持有時(shí)間較長時(shí),mutex 的這種機(jī)制可以有效減少 CPU 的浪費(fèi),提高系統(tǒng)整體性能 。所以,在鎖持有時(shí)間較長、資源競爭不頻繁的場景下,mutex 是更好的選擇;而在鎖持有時(shí)間極短、對(duì)響應(yīng)速度要求極高且資源競爭頻繁的場景中,自旋鎖則更具優(yōu)勢 。
5.2 mutex 與讀寫鎖對(duì)比
讀寫鎖是一種比 mutex 更細(xì)粒度的并發(fā)控制機(jī)制,它將對(duì)共享資源的訪問分為 “讀操作” 和 “寫操作” 兩種類型 。讀寫鎖允許多個(gè)讀線程同時(shí)訪問共享資源,因?yàn)樽x操作不會(huì)修改數(shù)據(jù),所以多個(gè)讀操作之間不會(huì)產(chǎn)生沖突 。但寫操作會(huì)修改數(shù)據(jù),所以寫操作必須是獨(dú)占的,當(dāng)有寫線程在進(jìn)行寫操作時(shí),其他讀線程和寫線程都不能訪問共享資源,這就是讀寫鎖的讀寫互斥、寫寫互斥特性 。
以一個(gè)緩存系統(tǒng)為例,大量的線程可能會(huì)同時(shí)讀取緩存中的數(shù)據(jù),而只有在數(shù)據(jù)更新時(shí)才需要進(jìn)行寫操作 。在這種讀多寫少的場景下,使用讀寫鎖就可以大大提高系統(tǒng)的并發(fā)性能 。多個(gè)讀線程可以同時(shí)獲取讀鎖,并發(fā)地讀取緩存數(shù)據(jù),而寫線程在進(jìn)行寫操作前獲取寫鎖,確保數(shù)據(jù)更新的原子性和一致性 。
mutex 則是一種更通用的互斥機(jī)制,它不區(qū)分讀操作和寫操作,無論是讀還是寫,同一時(shí)刻都只允許一個(gè)線程進(jìn)入臨界區(qū) 。在讀寫操作頻率相近或者寫操作比較頻繁的場景下,使用 mutex 可以簡化編程邏輯,因?yàn)椴恍枰~外處理讀寫鎖的復(fù)雜邏輯 。但在這種情況下,如果使用讀寫鎖,由于寫操作的獨(dú)占性,可能會(huì)導(dǎo)致讀線程長時(shí)間等待,降低系統(tǒng)的并發(fā)性能 。所以,在選擇同步機(jī)制時(shí),需要根據(jù)具體的讀寫操作需求和場景特點(diǎn)來決定是使用 mutex 還是讀寫鎖 。















 
 
 



















 
 
 
 