從騰訊面試題入手,帶你吃透C++互斥鎖
競爭激烈的互聯(lián)網(wǎng)求職市場中,騰訊的面試一直備受關(guān)注。對于 C++ 開發(fā)崗位的求職者來說,準備面試的過程充滿了挑戰(zhàn)。而在眾多可能被問到的問題中,“解釋 C++ 中的互斥鎖(Mutex)和其如何使用?” 這一問題頻繁出現(xiàn),成為了不少面試者必須攻克的難關(guān)。
這看似簡單的問題背后,其實蘊含著騰訊對候選人多方面能力的考察,不僅涉及到對基礎(chǔ)概念的理解,還關(guān)乎能否在實際項目中靈活運用這些知識。它就像一把鑰匙,解鎖著面試官對面試者 C++ 多線程編程水平的深度認知,也開啟了面試者通往騰訊的職業(yè)大門。
一、互斥鎖是什么
互斥鎖,即 Mutex,是英文 Mutual Exclusion 的縮寫,直譯為 “相互排斥” ,它是一種在多線程編程中至關(guān)重要的同步原語。在多線程環(huán)境下,當多個線程同時訪問和修改共享資源時,就可能會出現(xiàn)數(shù)據(jù)競爭問題,導(dǎo)致程序出現(xiàn)不可預(yù)測的行為。例如,在一個銀行賬戶轉(zhuǎn)賬的場景中,如果有多個線程同時對賬戶余額進行操作,可能會導(dǎo)致余額計算錯誤,出現(xiàn)重復(fù)扣款或多扣款的情況。
而互斥鎖的作用,就是為了避免這種數(shù)據(jù)競爭,確保在同一時刻,只有一個線程能夠訪問被保護的共享資源,就像給共享資源加上了一把鎖,當一個線程拿到這把鎖并進入臨界區(qū)(訪問共享資源的代碼區(qū)域)時,其他線程必須等待,直到該線程釋放鎖,其他線程才有機會獲取鎖并進入臨界區(qū)。 它就像是一個交通警察,在多線程的 “道路” 上指揮著對共享資源的訪問,保證秩序井然,避免混亂和沖突。
二、互斥鎖的工作原理
互斥鎖的工作原理基于操作系統(tǒng)提供的原子操作和線程調(diào)度機制。當一個線程執(zhí)行到需要訪問共享資源的代碼段時,它會調(diào)用互斥鎖的加鎖函數(shù)(如std::mutex的lock方法)。此時,互斥鎖會檢查自身的狀態(tài),如果當前處于未鎖定狀態(tài),它會將自己標記為已鎖定,并允許該線程進入臨界區(qū)訪問共享資源。這個標記過程是通過原子操作實現(xiàn)的,確保在多線程環(huán)境下不會出現(xiàn)競爭條件。例如,在一個多線程的文件讀寫操作中,當一個線程獲取到互斥鎖后,就可以安全地對文件進行寫入,避免其他線程同時寫入導(dǎo)致文件內(nèi)容混亂。
如果互斥鎖已經(jīng)被其他線程鎖定,那么調(diào)用加鎖函數(shù)的線程會被操作系統(tǒng)掛起,放入等待隊列中,進入阻塞狀態(tài)。此時,該線程會讓出 CPU 資源,以便其他線程能夠繼續(xù)執(zhí)行,避免了無效的 CPU 占用。就像在一條單行道上,當一輛車已經(jīng)在行駛時,其他車輛只能在路口等待,直到前面的車通過。
當持有鎖的線程完成對共享資源的訪問后,它會調(diào)用互斥鎖的解鎖函數(shù)(如std::mutex的unlock方法) 。解鎖操作會將互斥鎖的狀態(tài)標記為未鎖定,并從等待隊列中喚醒一個等待的線程(如果有線程在等待)。被喚醒的線程會重新競爭 CPU 資源,當它獲得 CPU 時間片后,會再次嘗試獲取互斥鎖。一旦獲取成功,就可以進入臨界區(qū)訪問共享資源。例如,在一個多線程的數(shù)據(jù)庫操作中,當一個線程完成對數(shù)據(jù)庫的更新操作并釋放互斥鎖后,等待隊列中的另一個線程就有機會獲取鎖,進行查詢或其他操作。
三、C++ 中互斥鎖的使用方法
1. std::mutex基礎(chǔ)使用
在 C++ 中,std::mutex是最基本的互斥鎖類型,定義在<mutex>頭文件中 。使用std::mutex時,需要先創(chuàng)建一個std::mutex對象,然后在需要保護的共享資源代碼段前后分別調(diào)用lock和unlock函數(shù)。例如,假設(shè)有一個多線程環(huán)境下的計數(shù)器,多個線程可能同時對其進行增加操作,為了保證線程安全,我們可以這樣使用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是一個std::mutex對象,用于保護counter這個共享資源。在increment函數(shù)中,每次對counter進行增加操作前,先調(diào)用mtx.lock()加鎖,確保同一時刻只有一個線程可以執(zhí)行++counter這一操作,操作完成后,再調(diào)用mtx.unlock()解鎖,釋放對counter的獨占訪問權(quán)。這樣就避免了多線程同時訪問counter導(dǎo)致的數(shù)據(jù)競爭問題,保證了counter值的正確性。
2. lock_guard的自動管理
雖然std::mutex的lock和unlock函數(shù)能夠?qū)崿F(xiàn)對共享資源的保護,但如果在unlock之前發(fā)生異常,就可能導(dǎo)致鎖無法釋放,從而產(chǎn)生死鎖。為了解決這個問題,C++ 標準庫提供了std::lock_guard類,它是一個基于 RAII(Resource Acquisition Is Initialization,資源獲取即初始化)機制的模板類,定義在<mutex>頭文件中 。std::lock_guard在構(gòu)造時會自動調(diào)用互斥鎖的lock函數(shù),在析構(gòu)時會自動調(diào)用互斥鎖的unlock函數(shù),從而確保在任何情況下(包括發(fā)生異常),鎖都能被正確釋放。 例如,我們可以將上述代碼中的std::mutex手動加解鎖改為使用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)建了一個std::lock_guard對象lock,并在構(gòu)造時自動對mtx加鎖。當lock的生命周期結(jié)束(即離開其作用域)時,會自動調(diào)用析構(gòu)函數(shù),在析構(gòu)函數(shù)中自動對mtx解鎖。這樣,即使在++counter這一操作過程中發(fā)生異常,mtx也會被正確解鎖,避免了死鎖的發(fā)生。
3. unique_lock的高級特性
std::unique_lock也是定義在<mutex>頭文件中的一個模板類,它比std::lock_guard提供了更靈活的鎖管理功能。
首先,std::unique_lock支持延遲加鎖。在創(chuàng)建std::unique_lock對象時,可以傳入第二個參數(shù)std::defer_lock,表示在構(gòu)造時不立即加鎖,而是在需要時手動調(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)建了一個std::unique_lock對象lock,但此時mtx并未加鎖。在執(zhí)行了一些不需要鎖的操作后,通過lock.lock()手動加鎖,進入臨界區(qū)訪問共享資源data,操作完成后,再通過lock.unlock()手動解鎖。這種延遲加鎖的特性可以減少鎖的持有時間,提高程序的并發(fā)性能。
其次,std::unique_lock提供了嘗試加鎖的功能??梢允褂胻ry_lock函數(shù)嘗試獲取鎖,如果鎖可用,則返回true,并獲取鎖;如果鎖不可用,則返回false,不會阻塞線程。例如:
#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;
}
}
在這個例子中,if (lock.try_lock())嘗試獲取鎖,如果成功獲取鎖,就執(zhí)行臨界區(qū)代碼,模擬一些工作后解鎖;如果未獲取鎖,就輸出提示信息,執(zhí)行其他邏輯。這種嘗試加鎖的功能在某些場景下非常有用,例如當一個線程在獲取鎖失敗時,可以選擇執(zhí)行一些其他任務(wù),而不是一直阻塞等待鎖的釋放。
此外,std::unique_lock還能與條件變量(std::condition_variable)配合使用,實現(xiàn)更復(fù)雜的線程同步機制。條件變量是一種多線程同步機制,它允許一個或多個線程等待另一個線程發(fā)出的通知。在使用條件變量時,需要使用std::unique_lock來管理互斥鎖。例如,在一個生產(chǎn)者 - 消費者模型中,生產(chǎn)者線程生產(chǎn)數(shù)據(jù)并將其放入共享隊列,消費者線程從共享隊列中取出數(shù)據(jù)進行消費。當共享隊列為空時,消費者線程需要等待生產(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(); // 通知一個等待的消費者線程
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
// 消費者線程函數(shù)
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return!dataQueue.empty(); }); // 等待條件滿足,即隊列不為空
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)建了一個std::unique_lock對象lock,用于管理互斥鎖mtx。在生產(chǎn)者線程中,生產(chǎn)數(shù)據(jù)后,通過cv.notify_one()通知一個等待的消費者線程;在消費者線程中,cv.wait(lock, [] { return!dataQueue.empty(); });等待條件變量cv的通知,并且在等待過程中會自動釋放鎖lock,當收到通知且條件滿足(隊列不為空)時,會重新獲取鎖lock,然后從隊列中取出數(shù)據(jù)進行消費。這里使用std::unique_lock能夠在條件變量等待過程中靈活地管理鎖的狀態(tài),確保線程安全和高效的同步。
四、互斥鎖的應(yīng)用場景
1. 數(shù)據(jù)庫連接池
在數(shù)據(jù)庫連接池的實現(xiàn)中,互斥鎖起著至關(guān)重要的作用。數(shù)據(jù)庫連接池是一種緩存數(shù)據(jù)庫連接的技術(shù),它可以避免頻繁地創(chuàng)建和銷毀數(shù)據(jù)庫連接,從而提高系統(tǒng)的性能和資源利用率。在多線程環(huán)境下,多個線程可能同時請求從連接池中獲取數(shù)據(jù)庫連接,或者將使用完的連接放回連接池。如果沒有互斥鎖的保護,就可能出現(xiàn)多個線程同時獲取到同一個連接,或者連接被錯誤地放回連接池,導(dǎo)致數(shù)據(jù)不一致和連接泄漏等問題。
通過使用互斥鎖,當一個線程請求獲取數(shù)據(jù)庫連接時,先獲取互斥鎖,然后從連接池中取出一個連接,并將該連接標記為已使用,再釋放互斥鎖;當線程使用完連接并將其放回連接池時,同樣先獲取互斥鎖,然后將連接標記為未使用,再將其放回連接池,最后釋放互斥鎖。這樣就保證了在多線程環(huán)境下,數(shù)據(jù)庫連接池的操作是線程安全的,確保了數(shù)據(jù)庫連接的正確管理和高效使用。例如,在一個高并發(fā)的電商系統(tǒng)中,大量的用戶請求需要查詢商品信息、更新訂單狀態(tài)等數(shù)據(jù)庫操作,數(shù)據(jù)庫連接池通過互斥鎖的保護,能夠穩(wěn)定地為各個線程提供數(shù)據(jù)庫連接服務(wù),保證系統(tǒng)的正常運行。
2. 文件讀寫操作
在多線程環(huán)境下進行文件讀寫操作時,互斥鎖可以確保文件的完整性和數(shù)據(jù)的一致性。當多個線程同時對同一個文件進行寫入操作時,如果沒有互斥鎖的保護,可能會導(dǎo)致文件內(nèi)容混亂,數(shù)據(jù)丟失或錯誤。例如,一個日志文件,多個線程可能同時產(chǎn)生日志信息并嘗試寫入該文件,如果不加控制,不同線程寫入的日志內(nèi)容可能會相互交錯,無法準確記錄系統(tǒng)的運行狀態(tài)。
使用互斥鎖后,當一個線程要寫入文件時,先獲取互斥鎖,然后進行寫入操作,完成后釋放互斥鎖。這樣,在同一時刻只有一個線程能夠?qū)懭胛募?,保證了文件內(nèi)容的有序性和正確性。同樣,在讀取文件時,如果存在多個線程同時讀取文件的情況,雖然讀取操作本身不會修改文件內(nèi)容,但如果在讀取過程中文件被其他線程修改,也可能導(dǎo)致讀取到不一致的數(shù)據(jù)。通過使用互斥鎖,可以在讀取文件時對文件進行鎖定,防止其他線程在讀取期間對文件進行修改,確保讀取到的數(shù)據(jù)是完整和一致的。例如,在一個分布式系統(tǒng)中,多個節(jié)點的線程可能會同時訪問一個共享的配置文件,互斥鎖能夠保障各個線程在讀取或修改配置文件時的正確性,避免因并發(fā)訪問導(dǎo)致的配置錯誤。
3. 共享內(nèi)存管理
在多進程或多線程環(huán)境下,共享內(nèi)存是一種高效的進程間或線程間通信方式,但同時也帶來了數(shù)據(jù)一致性的問題?;コ怄i在共享內(nèi)存管理中用于保護共享內(nèi)存區(qū)域,防止多個進程或線程同時對共享內(nèi)存進行讀寫操作,從而避免數(shù)據(jù)沖突和不一致。
例如,在一個實時監(jiān)控系統(tǒng)中,多個線程可能需要讀取和更新共享內(nèi)存中的監(jiān)控數(shù)據(jù)。如果沒有互斥鎖的保護,當一個線程正在更新監(jiān)控數(shù)據(jù)時,另一個線程可能同時讀取這些未完全更新的數(shù)據(jù),導(dǎo)致獲取到錯誤的監(jiān)控信息。通過在訪問共享內(nèi)存區(qū)域前后使用互斥鎖,當一個線程要訪問共享內(nèi)存時,先獲取互斥鎖,確保在其訪問期間其他線程無法同時訪問,訪問完成后釋放互斥鎖,這樣就保證了共享內(nèi)存數(shù)據(jù)的一致性和正確性。在操作系統(tǒng)內(nèi)核中,也經(jīng)常會使用互斥鎖來管理共享內(nèi)存資源,確保內(nèi)核數(shù)據(jù)結(jié)構(gòu)的完整性和系統(tǒng)的穩(wěn)定性。
五、使用互斥鎖的注意事項
1. 死鎖問題
死鎖是使用互斥鎖時最常見且最嚴重的問題之一。死鎖發(fā)生的場景通常有兩種:一種是同一個線程在持有鎖的情況下再次嘗試獲取同一把鎖,例如在一個遞歸函數(shù)中,函數(shù)內(nèi)部在未解鎖的情況下遞歸調(diào)用自身并嘗試獲取鎖,就會導(dǎo)致線程永遠阻塞等待自己釋放鎖,從而陷入死鎖。另一種常見場景是多個線程之間形成循環(huán)等待的關(guān)系,例如線程 A 持有鎖 1 并等待獲取鎖 2,而線程 B 持有鎖 2 并等待獲取鎖 1,這樣兩個線程就會相互等待,永遠無法繼續(xù)執(zhí)行。
死鎖產(chǎn)生的根本原因在于對鎖的使用不當,違背了資源分配的基本原則。死鎖產(chǎn)生的四個必要條件包括互斥條件(一個資源每次只能被一個進程使用)、請求與保持條件(一個進程因請求資源而阻塞時,對已獲得的資源保持不放)、不剝奪條件(進程已獲得的資源,在未使用完之前,不能強行剝奪)和循環(huán)等待條件(若干進程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系)。只要這四個條件同時成立,死鎖就會發(fā)生。
為了避免死鎖,可以采取多種策略。首先是資源一次性分配,一次性獲取所有需要的資源,避免在持有部分資源的情況下再請求其他資源,從而破壞請求與保持條件 。例如,在一個多線程的圖形渲染程序中,如果一個線程需要同時訪問圖形數(shù)據(jù)和渲染配置,那么在開始處理之前,一次性獲取這兩個資源的鎖,而不是先獲取圖形數(shù)據(jù)鎖,再嘗試獲取渲染配置鎖,這樣就可以避免因分步獲取鎖而導(dǎo)致的死鎖。
其次是可剝奪資源策略,當一個進程新的資源未滿足時,釋放已占有的資源,破壞不可剝奪條件。比如在一個任務(wù)調(diào)度系統(tǒng)中,當一個高優(yōu)先級任務(wù)需要資源但資源被低優(yōu)先級任務(wù)占用時,低優(yōu)先級任務(wù)可以主動釋放資源,讓高優(yōu)先級任務(wù)先執(zhí)行,從而避免死鎖。
還有資源有序分配法,系統(tǒng)給每類資源賦予一個編號,每一個進程按編號遞增的順序請求資源,釋放則相反,以此破壞環(huán)路等待條件。例如,在一個多線程的數(shù)據(jù)庫事務(wù)處理中,規(guī)定所有線程先獲取編號低的數(shù)據(jù)庫表鎖,再獲取編號高的表鎖,這樣就可以避免因不同線程以不同順序獲取鎖而導(dǎo)致的循環(huán)等待死鎖。
在實際編程中,使用std::lock函數(shù)可以同時對多個互斥鎖進行加鎖,并且它會自動處理死鎖問題,保證要么所有鎖都成功獲取,要么一個都不獲取。例如:
#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;
// 離開作用域時,lock1和lock2會自動解鎖
}
在上述代碼中,std::lock(mutex1, mutex2);嘗試同時獲取mutex1和mutex2,如果其中一個鎖無法獲取,它會自動釋放已經(jīng)獲取的鎖,避免死鎖。然后通過std::lock_guard并傳入std::adopt_lock來管理已經(jīng)獲取的鎖,確保在離開作用域時鎖能被正確釋放。
2. 性能開銷
互斥鎖的使用雖然能保證線程安全,但也會帶來一定的性能開銷?;コ怄i的實現(xiàn)通常涉及系統(tǒng)調(diào)用,當一個線程獲取或釋放互斥鎖時,可能會引發(fā)上下文切換。上下文切換是指操作系統(tǒng)將當前線程的狀態(tài)保存起來,然后切換到另一個線程執(zhí)行,這個過程需要保存和恢復(fù)寄存器的值、內(nèi)存映射等信息,會消耗一定的 CPU 時間和資源。例如,在一個高并發(fā)的 Web 服務(wù)器中,如果大量線程頻繁地獲取和釋放互斥鎖,就會導(dǎo)致頻繁的上下文切換,使 CPU 忙于線程調(diào)度,而不是執(zhí)行實際的業(yè)務(wù)邏輯,從而降低系統(tǒng)的整體性能。
為了減少性能開銷,在設(shè)計程序時,應(yīng)該盡量縮短臨界區(qū)的代碼長度,只將真正需要保護的共享資源訪問代碼放在臨界區(qū)內(nèi)。例如,在一個多線程的日志記錄系統(tǒng)中,將日志格式化和寫入文件的操作都放在臨界區(qū)內(nèi)是不必要的,可以先在臨界區(qū)外完成日志的格式化,然后在臨界區(qū)內(nèi)快速地將格式化后的日志寫入文件,這樣就能減少鎖的持有時間,降低性能開銷。
此外,對于一些讀多寫少的場景,可以考慮使用讀寫鎖(std::shared_mutex)來替代普通的互斥鎖。讀寫鎖允許多個線程同時進行讀操作,只有在寫操作時才會獨占資源,這樣可以提高并發(fā)性能。例如,在一個多線程的數(shù)據(jù)庫查詢系統(tǒng)中,大量線程可能同時讀取數(shù)據(jù)庫中的數(shù)據(jù),只有少數(shù)線程會進行數(shù)據(jù)更新操作,使用讀寫鎖可以讓多個讀線程同時獲取讀鎖,并行地讀取數(shù)據(jù),而寫線程在進行更新操作時獲取寫鎖,獨占資源,保證數(shù)據(jù)的一致性,同時提高了系統(tǒng)的并發(fā)處理能力。
3. 鎖的粒度選擇
鎖的粒度是指被鎖保護的代碼塊或資源的大小。選擇合適的鎖粒度對于程序的性能和正確性至關(guān)重要。如果鎖的粒度過大,將過多的代碼或資源都置于同一把鎖的保護之下,會導(dǎo)致并發(fā)性降低。因為只要有一個線程持有鎖,其他線程就必須等待,即使這些線程訪問的是不同的資源或執(zhí)行的是相互獨立的代碼。例如,在一個多線程的圖形處理程序中,如果將整個圖形渲染流程都用一把鎖保護起來,那么當一個線程正在進行圖形渲染時,其他線程無法進行任何與圖形相關(guān)的操作,包括一些簡單的圖形數(shù)據(jù)查詢,這會嚴重影響程序的并發(fā)性能。
相反,如果鎖的粒度過小,會增加鎖的管理開銷,并且可能導(dǎo)致死鎖的風險增加。因為多個細粒度的鎖可能會被不同的線程以不同的順序獲取,從而形成循環(huán)等待的死鎖場景。例如,在一個復(fù)雜的數(shù)據(jù)結(jié)構(gòu)中,如果對每個數(shù)據(jù)元素都使用一把單獨的鎖,雖然提高了并發(fā)性,但在多線程訪問時,可能會出現(xiàn)線程 A 持有元素 1 的鎖并等待元素 2 的鎖,而線程 B 持有元素 2 的鎖并等待元素 1 的鎖的死鎖情況。
在實際應(yīng)用中,需要根據(jù)具體的業(yè)務(wù)場景和數(shù)據(jù)訪問模式來選擇合適的鎖粒度。一般來說,可以將相關(guān)的資源或操作劃分為不同的模塊,為每個模塊設(shè)置一把鎖,這樣既能保證一定的并發(fā)性,又能降低鎖的管理開銷和死鎖風險。例如,在一個電商系統(tǒng)中,可以將商品管理、訂單管理和用戶管理分別用不同的鎖進行保護,不同模塊的線程可以并行執(zhí)行,而同一模塊內(nèi)的線程則通過鎖來保證數(shù)據(jù)的一致性。