偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

從騰訊面試題入手,帶你吃透C++互斥鎖

開發(fā)
互斥鎖,即 Mutex,是英文 Mutual Exclusion 的縮寫,直譯為 “相互排斥” ,它是一種在多線程編程中至關(guān)重要的同步原語。

競(jìng)爭(zhēng)激烈的互聯(lián)網(wǎng)求職市場(chǎng)中,騰訊的面試一直備受關(guān)注。對(duì)于 C++ 開發(fā)崗位的求職者來說,準(zhǔn)備面試的過程充滿了挑戰(zhàn)。而在眾多可能被問到的問題中,“解釋 C++ 中的互斥鎖(Mutex)和其如何使用?” 這一問題頻繁出現(xiàn),成為了不少面試者必須攻克的難關(guān)。

這看似簡(jiǎn)單的問題背后,其實(shí)蘊(yùn)含著騰訊對(duì)候選人多方面能力的考察,不僅涉及到對(duì)基礎(chǔ)概念的理解,還關(guān)乎能否在實(shí)際項(xiàng)目中靈活運(yùn)用這些知識(shí)。它就像一把鑰匙,解鎖著面試官對(duì)面試者 C++ 多線程編程水平的深度認(rèn)知,也開啟了面試者通往騰訊的職業(yè)大門。

一、互斥鎖是什么

互斥鎖,即 Mutex,是英文 Mutual Exclusion 的縮寫,直譯為 “相互排斥” ,它是一種在多線程編程中至關(guān)重要的同步原語。在多線程環(huán)境下,當(dāng)多個(gè)線程同時(shí)訪問和修改共享資源時(shí),就可能會(huì)出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)問題,導(dǎo)致程序出現(xiàn)不可預(yù)測(cè)的行為。例如,在一個(gè)銀行賬戶轉(zhuǎn)賬的場(chǎng)景中,如果有多個(gè)線程同時(shí)對(duì)賬戶余額進(jìn)行操作,可能會(huì)導(dǎo)致余額計(jì)算錯(cuò)誤,出現(xiàn)重復(fù)扣款或多扣款的情況。

而互斥鎖的作用,就是為了避免這種數(shù)據(jù)競(jìng)爭(zhēng),確保在同一時(shí)刻,只有一個(gè)線程能夠訪問被保護(hù)的共享資源,就像給共享資源加上了一把鎖,當(dāng)一個(gè)線程拿到這把鎖并進(jìn)入臨界區(qū)(訪問共享資源的代碼區(qū)域)時(shí),其他線程必須等待,直到該線程釋放鎖,其他線程才有機(jī)會(huì)獲取鎖并進(jìn)入臨界區(qū)。 它就像是一個(gè)交通警察,在多線程的 “道路” 上指揮著對(duì)共享資源的訪問,保證秩序井然,避免混亂和沖突。

二、互斥鎖的工作原理

互斥鎖的工作原理基于操作系統(tǒng)提供的原子操作和線程調(diào)度機(jī)制。當(dāng)一個(gè)線程執(zhí)行到需要訪問共享資源的代碼段時(shí),它會(huì)調(diào)用互斥鎖的加鎖函數(shù)(如std::mutex的lock方法)。此時(shí),互斥鎖會(huì)檢查自身的狀態(tài),如果當(dāng)前處于未鎖定狀態(tài),它會(huì)將自己標(biāo)記為已鎖定,并允許該線程進(jìn)入臨界區(qū)訪問共享資源。這個(gè)標(biāo)記過程是通過原子操作實(shí)現(xiàn)的,確保在多線程環(huán)境下不會(huì)出現(xiàn)競(jìng)爭(zhēng)條件。例如,在一個(gè)多線程的文件讀寫操作中,當(dāng)一個(gè)線程獲取到互斥鎖后,就可以安全地對(duì)文件進(jìn)行寫入,避免其他線程同時(shí)寫入導(dǎo)致文件內(nèi)容混亂。

如果互斥鎖已經(jīng)被其他線程鎖定,那么調(diào)用加鎖函數(shù)的線程會(huì)被操作系統(tǒng)掛起,放入等待隊(duì)列中,進(jìn)入阻塞狀態(tài)。此時(shí),該線程會(huì)讓出 CPU 資源,以便其他線程能夠繼續(xù)執(zhí)行,避免了無效的 CPU 占用。就像在一條單行道上,當(dāng)一輛車已經(jīng)在行駛時(shí),其他車輛只能在路口等待,直到前面的車通過。

當(dāng)持有鎖的線程完成對(duì)共享資源的訪問后,它會(huì)調(diào)用互斥鎖的解鎖函數(shù)(如std::mutex的unlock方法) 。解鎖操作會(huì)將互斥鎖的狀態(tài)標(biāo)記為未鎖定,并從等待隊(duì)列中喚醒一個(gè)等待的線程(如果有線程在等待)。被喚醒的線程會(huì)重新競(jìng)爭(zhēng) CPU 資源,當(dāng)它獲得 CPU 時(shí)間片后,會(huì)再次嘗試獲取互斥鎖。一旦獲取成功,就可以進(jìn)入臨界區(qū)訪問共享資源。例如,在一個(gè)多線程的數(shù)據(jù)庫操作中,當(dāng)一個(gè)線程完成對(duì)數(shù)據(jù)庫的更新操作并釋放互斥鎖后,等待隊(duì)列中的另一個(gè)線程就有機(jī)會(huì)獲取鎖,進(jìn)行查詢或其他操作。

三、C++ 中互斥鎖的使用方法

1. std::mutex基礎(chǔ)使用

在 C++ 中,std::mutex是最基本的互斥鎖類型,定義在<mutex>頭文件中 。使用std::mutex時(shí),需要先創(chuàng)建一個(gè)std::mutex對(duì)象,然后在需要保護(hù)的共享資源代碼段前后分別調(diào)用lock和unlock函數(shù)。例如,假設(shè)有一個(gè)多線程環(huán)境下的計(jì)數(shù)器,多個(gè)線程可能同時(shí)對(duì)其進(jìn)行增加操作,為了保證線程安全,我們可以這樣使用std::mutex:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int counter = 0;

void increment() {
    for (int i = 0; i < 1000; ++i) {
        mtx.lock();
        ++counter;
        mtx.unlock();
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}

在上述代碼中,mtx是一個(gè)std::mutex對(duì)象,用于保護(hù)counter這個(gè)共享資源。在increment函數(shù)中,每次對(duì)counter進(jìn)行增加操作前,先調(diào)用mtx.lock()加鎖,確保同一時(shí)刻只有一個(gè)線程可以執(zhí)行++counter這一操作,操作完成后,再調(diào)用mtx.unlock()解鎖,釋放對(duì)counter的獨(dú)占訪問權(quán)。這樣就避免了多線程同時(shí)訪問counter導(dǎo)致的數(shù)據(jù)競(jìng)爭(zhēng)問題,保證了counter值的正確性。

2. lock_guard的自動(dòng)管理

雖然std::mutex的lock和unlock函數(shù)能夠?qū)崿F(xiàn)對(duì)共享資源的保護(hù),但如果在unlock之前發(fā)生異常,就可能導(dǎo)致鎖無法釋放,從而產(chǎn)生死鎖。為了解決這個(gè)問題,C++ 標(biāo)準(zhǔn)庫提供了std::lock_guard類,它是一個(gè)基于 RAII(Resource Acquisition Is Initialization,資源獲取即初始化)機(jī)制的模板類,定義在<mutex>頭文件中 。std::lock_guard在構(gòu)造時(shí)會(huì)自動(dòng)調(diào)用互斥鎖的lock函數(shù),在析構(gòu)時(shí)會(huì)自動(dòng)調(diào)用互斥鎖的unlock函數(shù),從而確保在任何情況下(包括發(fā)生異常),鎖都能被正確釋放。 例如,我們可以將上述代碼中的std::mutex手動(dòng)加解鎖改為使用std::lock_guard:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int counter = 0;

void increment() {
    for (int i = 0; i < 1000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        ++counter;
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}

在這段代碼中,std::lock_guard<std::mutex> lock(mtx);這一行創(chuàng)建了一個(gè)std::lock_guard對(duì)象lock,并在構(gòu)造時(shí)自動(dòng)對(duì)mtx加鎖。當(dāng)lock的生命周期結(jié)束(即離開其作用域)時(shí),會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù),在析構(gòu)函數(shù)中自動(dòng)對(duì)mtx解鎖。這樣,即使在++counter這一操作過程中發(fā)生異常,mtx也會(huì)被正確解鎖,避免了死鎖的發(fā)生。

3. unique_lock的高級(jí)特性

std::unique_lock也是定義在<mutex>頭文件中的一個(gè)模板類,它比std::lock_guard提供了更靈活的鎖管理功能。

首先,std::unique_lock支持延遲加鎖。在創(chuàng)建std::unique_lock對(duì)象時(shí),可以傳入第二個(gè)參數(shù)std::defer_lock,表示在構(gòu)造時(shí)不立即加鎖,而是在需要時(shí)手動(dòng)調(diào)用lock函數(shù)加鎖。例如:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int data = 0;

void processData() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
    // 可以在這里執(zhí)行一些不需要鎖的操作
    lock.lock();
    // 臨界區(qū),訪問共享資源
    data += 10;
    lock.unlock();
    // 可以在這里執(zhí)行一些不需要鎖的操作
}

在上述代碼中,std::unique_lock<std::mutex> lock(mtx, std::defer_lock);創(chuàng)建了一個(gè)std::unique_lock對(duì)象lock,但此時(shí)mtx并未加鎖。在執(zhí)行了一些不需要鎖的操作后,通過lock.lock()手動(dòng)加鎖,進(jìn)入臨界區(qū)訪問共享資源data,操作完成后,再通過lock.unlock()手動(dòng)解鎖。這種延遲加鎖的特性可以減少鎖的持有時(shí)間,提高程序的并發(fā)性能。

其次,std::unique_lock提供了嘗試加鎖的功能。可以使用try_lock函數(shù)嘗試獲取鎖,如果鎖可用,則返回true,并獲取鎖;如果鎖不可用,則返回false,不會(huì)阻塞線程。例如:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void attemptAccess() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
    if (lock.try_lock()) {
        // 成功獲取鎖,執(zhí)行臨界區(qū)代碼
        std::cout << "Thread has acquired the lock." << std::endl;
        // 模擬一些工作
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        lock.unlock();
    } else {
        // 未獲取鎖,執(zhí)行其他邏輯
        std::cout << "Thread could not acquire the lock." << std::endl;
    }
}

在這個(gè)例子中,if (lock.try_lock())嘗試獲取鎖,如果成功獲取鎖,就執(zhí)行臨界區(qū)代碼,模擬一些工作后解鎖;如果未獲取鎖,就輸出提示信息,執(zhí)行其他邏輯。這種嘗試加鎖的功能在某些場(chǎng)景下非常有用,例如當(dāng)一個(gè)線程在獲取鎖失敗時(shí),可以選擇執(zhí)行一些其他任務(wù),而不是一直阻塞等待鎖的釋放。

此外,std::unique_lock還能與條件變量(std::condition_variable)配合使用,實(shí)現(xiàn)更復(fù)雜的線程同步機(jī)制。條件變量是一種多線程同步機(jī)制,它允許一個(gè)或多個(gè)線程等待另一個(gè)線程發(fā)出的通知。在使用條件變量時(shí),需要使用std::unique_lock來管理互斥鎖。例如,在一個(gè)生產(chǎn)者 - 消費(fèi)者模型中,生產(chǎn)者線程生產(chǎn)數(shù)據(jù)并將其放入共享隊(duì)列,消費(fèi)者線程從共享隊(duì)列中取出數(shù)據(jù)進(jìn)行消費(fèi)。當(dāng)共享隊(duì)列為空時(shí),消費(fèi)者線程需要等待生產(chǎn)者線程生產(chǎn)數(shù)據(jù)并發(fā)出通知。代碼示例如下:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> dataQueue;

// 生產(chǎn)者線程函數(shù)
void producer() {
    for (int i = 1; i <= 5; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        dataQueue.push(i);
        std::cout << "Produced: " << i << std::endl;
        lock.unlock();
        cv.notify_one(); // 通知一個(gè)等待的消費(fèi)者線程
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

// 消費(fèi)者線程函數(shù)
void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return!dataQueue.empty(); }); // 等待條件滿足,即隊(duì)列不為空
        int data = dataQueue.front();
        dataQueue.pop();
        std::cout << "Consumed: " << data << std::endl;
        lock.unlock();
        if (data == 5) {
            break;
        }
    }
}

在上述代碼中,std::unique_lock<std::mutex> lock(mtx);創(chuàng)建了一個(gè)std::unique_lock對(duì)象lock,用于管理互斥鎖mtx。在生產(chǎn)者線程中,生產(chǎn)數(shù)據(jù)后,通過cv.notify_one()通知一個(gè)等待的消費(fèi)者線程;在消費(fèi)者線程中,cv.wait(lock, [] { return!dataQueue.empty(); });等待條件變量cv的通知,并且在等待過程中會(huì)自動(dòng)釋放鎖lock,當(dāng)收到通知且條件滿足(隊(duì)列不為空)時(shí),會(huì)重新獲取鎖lock,然后從隊(duì)列中取出數(shù)據(jù)進(jìn)行消費(fèi)。這里使用std::unique_lock能夠在條件變量等待過程中靈活地管理鎖的狀態(tài),確保線程安全和高效的同步。

四、互斥鎖的應(yīng)用場(chǎng)景

1. 數(shù)據(jù)庫連接池

在數(shù)據(jù)庫連接池的實(shí)現(xiàn)中,互斥鎖起著至關(guān)重要的作用。數(shù)據(jù)庫連接池是一種緩存數(shù)據(jù)庫連接的技術(shù),它可以避免頻繁地創(chuàng)建和銷毀數(shù)據(jù)庫連接,從而提高系統(tǒng)的性能和資源利用率。在多線程環(huán)境下,多個(gè)線程可能同時(shí)請(qǐng)求從連接池中獲取數(shù)據(jù)庫連接,或者將使用完的連接放回連接池。如果沒有互斥鎖的保護(hù),就可能出現(xiàn)多個(gè)線程同時(shí)獲取到同一個(gè)連接,或者連接被錯(cuò)誤地放回連接池,導(dǎo)致數(shù)據(jù)不一致和連接泄漏等問題。

通過使用互斥鎖,當(dāng)一個(gè)線程請(qǐng)求獲取數(shù)據(jù)庫連接時(shí),先獲取互斥鎖,然后從連接池中取出一個(gè)連接,并將該連接標(biāo)記為已使用,再釋放互斥鎖;當(dāng)線程使用完連接并將其放回連接池時(shí),同樣先獲取互斥鎖,然后將連接標(biāo)記為未使用,再將其放回連接池,最后釋放互斥鎖。這樣就保證了在多線程環(huán)境下,數(shù)據(jù)庫連接池的操作是線程安全的,確保了數(shù)據(jù)庫連接的正確管理和高效使用。例如,在一個(gè)高并發(fā)的電商系統(tǒng)中,大量的用戶請(qǐng)求需要查詢商品信息、更新訂單狀態(tài)等數(shù)據(jù)庫操作,數(shù)據(jù)庫連接池通過互斥鎖的保護(hù),能夠穩(wěn)定地為各個(gè)線程提供數(shù)據(jù)庫連接服務(wù),保證系統(tǒng)的正常運(yùn)行。

2. 文件讀寫操作

在多線程環(huán)境下進(jìn)行文件讀寫操作時(shí),互斥鎖可以確保文件的完整性和數(shù)據(jù)的一致性。當(dāng)多個(gè)線程同時(shí)對(duì)同一個(gè)文件進(jìn)行寫入操作時(shí),如果沒有互斥鎖的保護(hù),可能會(huì)導(dǎo)致文件內(nèi)容混亂,數(shù)據(jù)丟失或錯(cuò)誤。例如,一個(gè)日志文件,多個(gè)線程可能同時(shí)產(chǎn)生日志信息并嘗試寫入該文件,如果不加控制,不同線程寫入的日志內(nèi)容可能會(huì)相互交錯(cuò),無法準(zhǔn)確記錄系統(tǒng)的運(yùn)行狀態(tài)。

使用互斥鎖后,當(dāng)一個(gè)線程要寫入文件時(shí),先獲取互斥鎖,然后進(jìn)行寫入操作,完成后釋放互斥鎖。這樣,在同一時(shí)刻只有一個(gè)線程能夠?qū)懭胛募?,保證了文件內(nèi)容的有序性和正確性。同樣,在讀取文件時(shí),如果存在多個(gè)線程同時(shí)讀取文件的情況,雖然讀取操作本身不會(huì)修改文件內(nèi)容,但如果在讀取過程中文件被其他線程修改,也可能導(dǎo)致讀取到不一致的數(shù)據(jù)。通過使用互斥鎖,可以在讀取文件時(shí)對(duì)文件進(jìn)行鎖定,防止其他線程在讀取期間對(duì)文件進(jìn)行修改,確保讀取到的數(shù)據(jù)是完整和一致的。例如,在一個(gè)分布式系統(tǒng)中,多個(gè)節(jié)點(diǎn)的線程可能會(huì)同時(shí)訪問一個(gè)共享的配置文件,互斥鎖能夠保障各個(gè)線程在讀取或修改配置文件時(shí)的正確性,避免因并發(fā)訪問導(dǎo)致的配置錯(cuò)誤。

3. 共享內(nèi)存管理

在多進(jìn)程或多線程環(huán)境下,共享內(nèi)存是一種高效的進(jìn)程間或線程間通信方式,但同時(shí)也帶來了數(shù)據(jù)一致性的問題。互斥鎖在共享內(nèi)存管理中用于保護(hù)共享內(nèi)存區(qū)域,防止多個(gè)進(jìn)程或線程同時(shí)對(duì)共享內(nèi)存進(jìn)行讀寫操作,從而避免數(shù)據(jù)沖突和不一致。

例如,在一個(gè)實(shí)時(shí)監(jiān)控系統(tǒng)中,多個(gè)線程可能需要讀取和更新共享內(nèi)存中的監(jiān)控?cái)?shù)據(jù)。如果沒有互斥鎖的保護(hù),當(dāng)一個(gè)線程正在更新監(jiān)控?cái)?shù)據(jù)時(shí),另一個(gè)線程可能同時(shí)讀取這些未完全更新的數(shù)據(jù),導(dǎo)致獲取到錯(cuò)誤的監(jiān)控信息。通過在訪問共享內(nèi)存區(qū)域前后使用互斥鎖,當(dāng)一個(gè)線程要訪問共享內(nèi)存時(shí),先獲取互斥鎖,確保在其訪問期間其他線程無法同時(shí)訪問,訪問完成后釋放互斥鎖,這樣就保證了共享內(nèi)存數(shù)據(jù)的一致性和正確性。在操作系統(tǒng)內(nèi)核中,也經(jīng)常會(huì)使用互斥鎖來管理共享內(nèi)存資源,確保內(nèi)核數(shù)據(jù)結(jié)構(gòu)的完整性和系統(tǒng)的穩(wěn)定性。

五、使用互斥鎖的注意事項(xiàng)

1. 死鎖問題

死鎖是使用互斥鎖時(shí)最常見且最嚴(yán)重的問題之一。死鎖發(fā)生的場(chǎng)景通常有兩種:一種是同一個(gè)線程在持有鎖的情況下再次嘗試獲取同一把鎖,例如在一個(gè)遞歸函數(shù)中,函數(shù)內(nèi)部在未解鎖的情況下遞歸調(diào)用自身并嘗試獲取鎖,就會(huì)導(dǎo)致線程永遠(yuǎn)阻塞等待自己釋放鎖,從而陷入死鎖。另一種常見場(chǎng)景是多個(gè)線程之間形成循環(huán)等待的關(guān)系,例如線程 A 持有鎖 1 并等待獲取鎖 2,而線程 B 持有鎖 2 并等待獲取鎖 1,這樣兩個(gè)線程就會(huì)相互等待,永遠(yuǎn)無法繼續(xù)執(zhí)行。

死鎖產(chǎn)生的根本原因在于對(duì)鎖的使用不當(dāng),違背了資源分配的基本原則。死鎖產(chǎn)生的四個(gè)必要條件包括互斥條件(一個(gè)資源每次只能被一個(gè)進(jìn)程使用)、請(qǐng)求與保持條件(一個(gè)進(jìn)程因請(qǐng)求資源而阻塞時(shí),對(duì)已獲得的資源保持不放)、不剝奪條件(進(jìn)程已獲得的資源,在未使用完之前,不能強(qiáng)行剝奪)和循環(huán)等待條件(若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系)。只要這四個(gè)條件同時(shí)成立,死鎖就會(huì)發(fā)生。

為了避免死鎖,可以采取多種策略。首先是資源一次性分配,一次性獲取所有需要的資源,避免在持有部分資源的情況下再請(qǐng)求其他資源,從而破壞請(qǐng)求與保持條件 。例如,在一個(gè)多線程的圖形渲染程序中,如果一個(gè)線程需要同時(shí)訪問圖形數(shù)據(jù)和渲染配置,那么在開始處理之前,一次性獲取這兩個(gè)資源的鎖,而不是先獲取圖形數(shù)據(jù)鎖,再嘗試獲取渲染配置鎖,這樣就可以避免因分步獲取鎖而導(dǎo)致的死鎖。

其次是可剝奪資源策略,當(dāng)一個(gè)進(jìn)程新的資源未滿足時(shí),釋放已占有的資源,破壞不可剝奪條件。比如在一個(gè)任務(wù)調(diào)度系統(tǒng)中,當(dāng)一個(gè)高優(yōu)先級(jí)任務(wù)需要資源但資源被低優(yōu)先級(jí)任務(wù)占用時(shí),低優(yōu)先級(jí)任務(wù)可以主動(dòng)釋放資源,讓高優(yōu)先級(jí)任務(wù)先執(zhí)行,從而避免死鎖。

還有資源有序分配法,系統(tǒng)給每類資源賦予一個(gè)編號(hào),每一個(gè)進(jìn)程按編號(hào)遞增的順序請(qǐng)求資源,釋放則相反,以此破壞環(huán)路等待條件。例如,在一個(gè)多線程的數(shù)據(jù)庫事務(wù)處理中,規(guī)定所有線程先獲取編號(hào)低的數(shù)據(jù)庫表鎖,再獲取編號(hào)高的表鎖,這樣就可以避免因不同線程以不同順序獲取鎖而導(dǎo)致的循環(huán)等待死鎖。

在實(shí)際編程中,使用std::lock函數(shù)可以同時(shí)對(duì)多個(gè)互斥鎖進(jìn)行加鎖,并且它會(huì)自動(dòng)處理死鎖問題,保證要么所有鎖都成功獲取,要么一個(gè)都不獲取。例如:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mutex1;
std::mutex mutex2;

void threadFunction() {
    std::lock(mutex1, mutex2);
    std::lock_guard<std::mutex> lock1(mutex1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mutex2, std::adopt_lock);
    // 臨界區(qū),訪問共享資源
    std::cout << "Thread is in critical section." << std::endl;
    // 離開作用域時(shí),lock1和lock2會(huì)自動(dòng)解鎖
}

在上述代碼中,std::lock(mutex1, mutex2);嘗試同時(shí)獲取mutex1和mutex2,如果其中一個(gè)鎖無法獲取,它會(huì)自動(dòng)釋放已經(jīng)獲取的鎖,避免死鎖。然后通過std::lock_guard并傳入std::adopt_lock來管理已經(jīng)獲取的鎖,確保在離開作用域時(shí)鎖能被正確釋放。

2. 性能開銷

互斥鎖的使用雖然能保證線程安全,但也會(huì)帶來一定的性能開銷?;コ怄i的實(shí)現(xiàn)通常涉及系統(tǒng)調(diào)用,當(dāng)一個(gè)線程獲取或釋放互斥鎖時(shí),可能會(huì)引發(fā)上下文切換。上下文切換是指操作系統(tǒng)將當(dāng)前線程的狀態(tài)保存起來,然后切換到另一個(gè)線程執(zhí)行,這個(gè)過程需要保存和恢復(fù)寄存器的值、內(nèi)存映射等信息,會(huì)消耗一定的 CPU 時(shí)間和資源。例如,在一個(gè)高并發(fā)的 Web 服務(wù)器中,如果大量線程頻繁地獲取和釋放互斥鎖,就會(huì)導(dǎo)致頻繁的上下文切換,使 CPU 忙于線程調(diào)度,而不是執(zhí)行實(shí)際的業(yè)務(wù)邏輯,從而降低系統(tǒng)的整體性能。

為了減少性能開銷,在設(shè)計(jì)程序時(shí),應(yīng)該盡量縮短臨界區(qū)的代碼長(zhǎng)度,只將真正需要保護(hù)的共享資源訪問代碼放在臨界區(qū)內(nèi)。例如,在一個(gè)多線程的日志記錄系統(tǒng)中,將日志格式化和寫入文件的操作都放在臨界區(qū)內(nèi)是不必要的,可以先在臨界區(qū)外完成日志的格式化,然后在臨界區(qū)內(nèi)快速地將格式化后的日志寫入文件,這樣就能減少鎖的持有時(shí)間,降低性能開銷。

此外,對(duì)于一些讀多寫少的場(chǎng)景,可以考慮使用讀寫鎖(std::shared_mutex)來替代普通的互斥鎖。讀寫鎖允許多個(gè)線程同時(shí)進(jìn)行讀操作,只有在寫操作時(shí)才會(huì)獨(dú)占資源,這樣可以提高并發(fā)性能。例如,在一個(gè)多線程的數(shù)據(jù)庫查詢系統(tǒng)中,大量線程可能同時(shí)讀取數(shù)據(jù)庫中的數(shù)據(jù),只有少數(shù)線程會(huì)進(jìn)行數(shù)據(jù)更新操作,使用讀寫鎖可以讓多個(gè)讀線程同時(shí)獲取讀鎖,并行地讀取數(shù)據(jù),而寫線程在進(jìn)行更新操作時(shí)獲取寫鎖,獨(dú)占資源,保證數(shù)據(jù)的一致性,同時(shí)提高了系統(tǒng)的并發(fā)處理能力。

3. 鎖的粒度選擇

鎖的粒度是指被鎖保護(hù)的代碼塊或資源的大小。選擇合適的鎖粒度對(duì)于程序的性能和正確性至關(guān)重要。如果鎖的粒度過大,將過多的代碼或資源都置于同一把鎖的保護(hù)之下,會(huì)導(dǎo)致并發(fā)性降低。因?yàn)橹灰幸粋€(gè)線程持有鎖,其他線程就必須等待,即使這些線程訪問的是不同的資源或執(zhí)行的是相互獨(dú)立的代碼。例如,在一個(gè)多線程的圖形處理程序中,如果將整個(gè)圖形渲染流程都用一把鎖保護(hù)起來,那么當(dāng)一個(gè)線程正在進(jìn)行圖形渲染時(shí),其他線程無法進(jìn)行任何與圖形相關(guān)的操作,包括一些簡(jiǎn)單的圖形數(shù)據(jù)查詢,這會(huì)嚴(yán)重影響程序的并發(fā)性能。

相反,如果鎖的粒度過小,會(huì)增加鎖的管理開銷,并且可能導(dǎo)致死鎖的風(fēng)險(xiǎn)增加。因?yàn)槎鄠€(gè)細(xì)粒度的鎖可能會(huì)被不同的線程以不同的順序獲取,從而形成循環(huán)等待的死鎖場(chǎng)景。例如,在一個(gè)復(fù)雜的數(shù)據(jù)結(jié)構(gòu)中,如果對(duì)每個(gè)數(shù)據(jù)元素都使用一把單獨(dú)的鎖,雖然提高了并發(fā)性,但在多線程訪問時(shí),可能會(huì)出現(xiàn)線程 A 持有元素 1 的鎖并等待元素 2 的鎖,而線程 B 持有元素 2 的鎖并等待元素 1 的鎖的死鎖情況。

在實(shí)際應(yīng)用中,需要根據(jù)具體的業(yè)務(wù)場(chǎng)景和數(shù)據(jù)訪問模式來選擇合適的鎖粒度。一般來說,可以將相關(guān)的資源或操作劃分為不同的模塊,為每個(gè)模塊設(shè)置一把鎖,這樣既能保證一定的并發(fā)性,又能降低鎖的管理開銷和死鎖風(fēng)險(xiǎn)。例如,在一個(gè)電商系統(tǒng)中,可以將商品管理、訂單管理和用戶管理分別用不同的鎖進(jìn)行保護(hù),不同模塊的線程可以并行執(zhí)行,而同一模塊內(nèi)的線程則通過鎖來保證數(shù)據(jù)的一致性。

責(zé)任編輯:趙寧寧 來源: 深度Linux
相關(guān)推薦

2025-05-26 03:20:00

2021-10-27 11:00:30

C++語言面試

2010-01-28 16:58:32

學(xué)習(xí)C++感想

2024-06-24 08:10:00

C++互斥鎖

2025-05-23 08:15:00

C++constexpr字面類型

2025-05-20 10:00:00

C++命名空間別名代碼

2025-05-27 10:15:00

void*函數(shù)開發(fā)

2025-04-30 10:10:00

在 C++C++11Lambda

2011-03-29 14:31:41

CC++

2025-03-24 00:11:05

IO模型計(jì)算機(jī)

2025-05-20 08:10:00

函數(shù)函數(shù)類型函數(shù)指針類型

2025-06-05 08:05:00

vectorC++對(duì)象存儲(chǔ)

2020-06-04 14:40:40

面試題Vue前端

2020-08-26 08:59:58

Linux線程互斥鎖

2025-06-09 07:55:00

C++引用語言

2020-11-16 07:22:32

騰訊多線程

2011-03-24 13:27:37

SQL

2023-11-13 07:37:36

JS面試題線程

2009-08-28 09:29:02

2012-06-26 11:09:07

Web
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)