量:從并發(fā)編程基石到生產(chǎn)者-消費(fèi)者實(shí)戰(zhàn))
1. 項(xiàng)目概述從“搶車(chē)位”到“信號(hào)量”的認(rèn)知躍遷如果你寫(xiě)過(guò)并發(fā)程序或者調(diào)試過(guò)多線程的bug那你一定對(duì)“競(jìng)態(tài)條件”這個(gè)詞深?lèi)和唇^。想象一個(gè)經(jīng)典的場(chǎng)景一個(gè)公共停車(chē)場(chǎng)只有10個(gè)車(chē)位。如果沒(méi)有管理會(huì)發(fā)生什么車(chē)輛A和車(chē)輛B同時(shí)駛?cè)胨鼈兌肌翱吹健边€剩1個(gè)車(chē)位于是都開(kāi)了進(jìn)去結(jié)果就是第11輛車(chē)擠了進(jìn)來(lái)造成了刮蹭和混亂。在操作系統(tǒng)和并發(fā)編程的世界里這個(gè)“公共停車(chē)場(chǎng)”就是共享資源比如一塊內(nèi)存、一個(gè)文件、一個(gè)打印機(jī)而“車(chē)輛”就是并發(fā)的線程或進(jìn)程。信號(hào)量就是這個(gè)停車(chē)場(chǎng)的智能管理員它手里拿著一個(gè)計(jì)數(shù)器精確地控制著誰(shuí)能進(jìn)、誰(shuí)要等從而徹底解決這種混亂。今天我們就來(lái)徹底拆解這個(gè)并發(fā)編程中的基石概念——信號(hào)量不僅理解它是什么更要搞懂它如何優(yōu)雅地解決同步與互斥問(wèn)題并分享一些實(shí)戰(zhàn)中容易踩坑的注意點(diǎn)。2. 信號(hào)量的核心思想與工作機(jī)制拆解2.1 信號(hào)量究竟是什么不止是一個(gè)計(jì)數(shù)器很多人初學(xué)信號(hào)量會(huì)把它簡(jiǎn)單地理解為一個(gè)整型變量。這個(gè)理解對(duì)了一半但漏掉了最關(guān)鍵的靈魂。信號(hào)量Semaphore本質(zhì)上是一個(gè)同步原語(yǔ)它包含一個(gè)整型值我們稱之為value或count和一個(gè)等待隊(duì)列。它的核心操作被封裝為兩個(gè)原子操作P操作也稱為wait、acquire或down和V操作也稱為signal、release或up。為什么說(shuō)它不止是計(jì)數(shù)器因?yàn)閱渭兊挠?jì)數(shù)器加減無(wú)法解決“等待”的問(wèn)題。當(dāng)資源不可用時(shí)線程必須被安全地掛起而不是忙等待busy-waiting空耗CPU。信號(hào)量?jī)?nèi)部的等待隊(duì)列就是用來(lái)管理這些被阻塞的線程的。P操作在嘗試減少信號(hào)量值時(shí)如果發(fā)現(xiàn)值小于等于0當(dāng)前線程就會(huì)被放入等待隊(duì)列并掛起V操作在增加信號(hào)量值后會(huì)檢查等待隊(duì)列如果有線程在等待就會(huì)喚醒其中一個(gè)。這個(gè)“檢查值、修改值、掛起/喚醒線程”的過(guò)程必須是原子的即不可被中斷這是信號(hào)量能正確工作的根本保證通常由操作系統(tǒng)內(nèi)核或利用CPU的原子指令來(lái)實(shí)現(xiàn)。2.2 同步與互斥信號(hào)量解決的兩種經(jīng)典問(wèn)題信號(hào)量主要用來(lái)解決兩類(lèi)并發(fā)控制問(wèn)題互斥和同步。這是兩個(gè)不同的概念但信號(hào)量能一肩挑。互斥確保在任何時(shí)刻只有一個(gè)執(zhí)行流線程/進(jìn)程能進(jìn)入臨界區(qū)訪問(wèn)共享資源。這就像只有一個(gè)坑位的公共廁所一次只能進(jìn)一個(gè)人。用于互斥的信號(hào)量被稱為二元信號(hào)量或互斥鎖其初始值通常設(shè)為1。線程進(jìn)入臨界區(qū)前執(zhí)行P操作將值從1減為0鎖定離開(kāi)后執(zhí)行V操作將值從0加為1解鎖。如果第二個(gè)線程在鎖定時(shí)嘗試P操作它會(huì)被阻塞直到第一個(gè)線程執(zhí)行V操作。同步協(xié)調(diào)多個(gè)執(zhí)行流之間的執(zhí)行順序。比如線程A必須完成數(shù)據(jù)生產(chǎn)后線程B才能開(kāi)始消費(fèi)。用于同步的信號(hào)量被稱為計(jì)數(shù)信號(hào)量其初始值代表了可用資源的數(shù)量如初始為0表示尚無(wú)可用資源。在上面的生產(chǎn)者-消費(fèi)者例子中我們可以用一個(gè)信號(hào)量full來(lái)同步生產(chǎn)者生產(chǎn)一個(gè)數(shù)據(jù)后執(zhí)行V(full)增加可用數(shù)據(jù)計(jì)數(shù)消費(fèi)者在消費(fèi)前執(zhí)行P(full)如果full為0沒(méi)有數(shù)據(jù)則消費(fèi)者等待。注意雖然互斥鎖Mutex在概念和實(shí)現(xiàn)上非常接近初始值為1的二元信號(hào)量但在現(xiàn)代編程實(shí)踐中我們通常使用專門(mén)的互斥鎖原語(yǔ)如pthread_mutex_t來(lái)處理互斥因?yàn)樗鼈兊腁PI通常更清晰且可能包含所有權(quán)、遞歸鎖等更豐富的語(yǔ)義。而信號(hào)量更常被用于復(fù)雜的同步場(chǎng)景。2.3 信號(hào)量操作的底層邏輯與原子性保障理解P和V操作的偽代碼能讓我們看清其本質(zhì)// P(semaphore S) wait(S){ // 原子性地執(zhí)行以下操作 S.value--; if (S.value 0) { // 將當(dāng)前線程加入S的等待隊(duì)列 block(current_thread); } } // V(semaphore S) signal(S){ // 原子性地執(zhí)行以下操作 S.value; if (S.value 0) { // 從S的等待隊(duì)列中移除一個(gè)線程T T remove_a_thread_from(S.wait_queue); // 將線程T置為就緒狀態(tài) wakeup(T); } }這里有三個(gè)關(guān)鍵點(diǎn)原子性S.value--和其后的判斷、block操作必須是一個(gè)不可分割的整體。同樣S.value和判斷、wakeup也是。這是通過(guò)硬件指令如CAS, Test-and-Set或操作系統(tǒng)內(nèi)核提供的系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)的。等待隊(duì)列當(dāng)S.value減為負(fù)數(shù)時(shí)其絕對(duì)值就表示當(dāng)前有多少個(gè)線程正在等待該信號(hào)量。這是信號(hào)量能高效管理等待線程的核心數(shù)據(jù)結(jié)構(gòu)。喚醒策略V操作喚醒等待隊(duì)列中的一個(gè)線程。通常采用FIFO先進(jìn)先出策略以實(shí)現(xiàn)公平性但具體實(shí)現(xiàn)可能不同。3. 用信號(hào)量解決經(jīng)典同步問(wèn)題生產(chǎn)者-消費(fèi)者理論說(shuō)再多不如看一個(gè)實(shí)戰(zhàn)案例。生產(chǎn)者-消費(fèi)者問(wèn)題是并發(fā)編程的“Hello World”它完美融合了互斥和同步。3.1 問(wèn)題場(chǎng)景與核心矛盾假設(shè)我們有一個(gè)大小為N的緩沖區(qū)。生產(chǎn)者線程不斷生成數(shù)據(jù)放入緩沖區(qū)消費(fèi)者線程不斷從緩沖區(qū)取出數(shù)據(jù)消費(fèi)。我們需要保證互斥任何時(shí)刻只能有一個(gè)線程生產(chǎn)者或消費(fèi)者操作緩沖區(qū)防止數(shù)據(jù)覆蓋或錯(cuò)亂。同步緩沖區(qū)滿時(shí)生產(chǎn)者必須等待直到消費(fèi)者取走數(shù)據(jù)。緩沖區(qū)空時(shí)消費(fèi)者必須等待直到生產(chǎn)者放入數(shù)據(jù)。3.2 信號(hào)量方案設(shè)計(jì)與實(shí)現(xiàn)我們需要三個(gè)信號(hào)量來(lái)解決這個(gè)問(wèn)題mutex一個(gè)二元信號(hào)量初始值為1用于保證對(duì)緩沖區(qū)的互斥訪問(wèn)。empty一個(gè)計(jì)數(shù)信號(hào)量初始值為N代表緩沖區(qū)中空位的數(shù)量。full一個(gè)計(jì)數(shù)信號(hào)量初始值為0代表緩沖區(qū)中已存放數(shù)據(jù)的數(shù)量。生產(chǎn)者線程的代碼邏輯while(True){ // 生產(chǎn)一個(gè)數(shù)據(jù) item item produce_item(); P(empty); // 申請(qǐng)一個(gè)空位。如果empty0緩沖區(qū)滿則阻塞等待。 P(mutex); // 申請(qǐng)進(jìn)入臨界區(qū)操作緩沖區(qū)。 // 將item放入緩沖區(qū) insert_item(item); V(mutex); // 離開(kāi)臨界區(qū)釋放鎖。 V(full); // 增加一個(gè)已滿位通知消費(fèi)者有數(shù)據(jù)可取了。 }消費(fèi)者線程的代碼邏輯while(True){ P(full); // 申請(qǐng)一個(gè)數(shù)據(jù)。如果full0緩沖區(qū)空則阻塞等待。 P(mutex); // 申請(qǐng)進(jìn)入臨界區(qū)操作緩沖區(qū)。 // 從緩沖區(qū)取出一個(gè)數(shù)據(jù) item item remove_item(); V(mutex); // 離開(kāi)臨界區(qū)釋放鎖。 V(empty); // 增加一個(gè)空位通知生產(chǎn)者有空位可放了。 // 消費(fèi)數(shù)據(jù) item consume_item(item); }3.3 為什么P操作的順序至關(guān)重要細(xì)心的你可能發(fā)現(xiàn)了生產(chǎn)者和消費(fèi)者中兩個(gè)P操作的順序是固定的生產(chǎn)者先P(empty)再P(mutex)消費(fèi)者先P(full)再P(mutex)。這個(gè)順序絕對(duì)不能顛倒假設(shè)生產(chǎn)者將順序顛倒為先P(mutex)再P(empty)生產(chǎn)者成功獲取mutex進(jìn)入臨界區(qū)。執(zhí)行P(empty)時(shí)發(fā)現(xiàn)緩沖區(qū)已滿empty0于是生產(chǎn)者被阻塞在empty信號(hào)量上。此時(shí)mutex鎖還被這個(gè)生產(chǎn)者持有并未釋放。消費(fèi)者運(yùn)行嘗試執(zhí)行P(mutex)以進(jìn)入臨界區(qū)取數(shù)據(jù)但mutex已被阻塞的生產(chǎn)者持有于是消費(fèi)者也被阻塞。結(jié)果死鎖。生產(chǎn)者等待消費(fèi)者騰出空位消費(fèi)者等待生產(chǎn)者釋放鎖兩者互相等待程序永遠(yuǎn)卡住。因此一個(gè)重要的實(shí)操心得是在需要同時(shí)獲取多個(gè)信號(hào)量時(shí)應(yīng)遵循一個(gè)原則——先申請(qǐng)資源信號(hào)量如empty,full再申請(qǐng)互斥信號(hào)量mutex。這可以最大限度地減少持有互斥鎖的時(shí)間降低死鎖風(fēng)險(xiǎn)。釋放信號(hào)量V操作的順序則通常沒(méi)有嚴(yán)格要求。4. 信號(hào)量使用中的核心注意點(diǎn)與避坑指南理解了基本原理和經(jīng)典模型只是第一步。在實(shí)際編碼中信號(hào)量用不好帶來(lái)的問(wèn)題往往比不用更嚴(yán)重。下面是我在多年開(kāi)發(fā)中總結(jié)的幾個(gè)關(guān)鍵注意點(diǎn)。4.1 初始化錯(cuò)誤一切混亂的根源信號(hào)量的初始值是其語(yǔ)義的起點(diǎn)。初始化錯(cuò)誤會(huì)導(dǎo)致程序邏輯完全偏離預(yù)期。互斥信號(hào)量必須初始化為1。如果初始化為0那么所有線程在第一次P操作時(shí)都會(huì)被阻塞沒(méi)有線程能執(zhí)行V操作來(lái)解鎖導(dǎo)致全體死鎖。同步信號(hào)量初始值代表初始可用資源數(shù)。例如數(shù)據(jù)庫(kù)連接池大小為10則對(duì)應(yīng)的信號(hào)量應(yīng)初始化為10。如果初始化為負(fù)數(shù)邏輯上通常無(wú)意義且會(huì)導(dǎo)致立即阻塞。注意在一些編程語(yǔ)言或庫(kù)中如POSIX semaphoresem_init信號(hào)量初始值不允許為負(fù)數(shù)。但在理論模型和某些實(shí)現(xiàn)中負(fù)值表示等待線程的數(shù)量。4.2 忘記釋放信號(hào)量資源泄漏與死鎖這可能是最常見(jiàn)也最致命的錯(cuò)誤類(lèi)似于malloc后忘了free?;コ怄i未釋放線程在持有互斥鎖時(shí)因?yàn)楫惓?、提前返回或分支遺漏沒(méi)有執(zhí)行對(duì)應(yīng)的V操作。這將導(dǎo)致該鎖永遠(yuǎn)無(wú)法被獲取其他所有等待該鎖的線程永久掛起整個(gè)相關(guān)功能模塊僵死。同步信號(hào)量未釋放例如生產(chǎn)者生產(chǎn)了數(shù)據(jù)V(full)了但消費(fèi)者消費(fèi)后忘了V(empty)。幾次循環(huán)后empty信號(hào)量減到0生產(chǎn)者全部阻塞但消費(fèi)者還在等待full實(shí)際上full也不會(huì)再增加最終導(dǎo)致死鎖。避坑技巧使用RAII資源獲取即初始化范式在C等語(yǔ)言中利用對(duì)象的構(gòu)造和析構(gòu)函數(shù)自動(dòng)完成P和V操作。class SemaphoreGuard { public: SemaphoreGuard(Semaphore sem) : m_sem(sem) { m_sem.P(); } ~SemaphoreGuard() { m_sem.V(); } private: Semaphore m_sem; }; // 使用 { SemaphoreGuard lock(mutex); // 構(gòu)造時(shí)自動(dòng)P(mutex) // ... 操作臨界區(qū) ... } // 離開(kāi)作用域時(shí)lock析構(gòu)自動(dòng)V(mutex)即使發(fā)生異常也會(huì)調(diào)用清晰的代碼路徑審查對(duì)于復(fù)雜的邏輯分支if-else, switch, 循環(huán)中的break/continue必須仔細(xì)檢查每一條路徑是否都保證了信號(hào)量的配對(duì)釋放。4.3 優(yōu)先級(jí)反轉(zhuǎn)與信號(hào)量這是一個(gè)在實(shí)時(shí)系統(tǒng)中尤為突出的問(wèn)題。假設(shè)有三個(gè)線程高優(yōu)先級(jí)線程H中優(yōu)先級(jí)線程M低優(yōu)先級(jí)線程L。它們共享一個(gè)由信號(hào)量S保護(hù)的資源。L先運(yùn)行并成功P(S)獲得了資源。H開(kāi)始運(yùn)行它也需要資源S于是執(zhí)行P(S)但由于S被L持有H被阻塞。此時(shí)中優(yōu)先級(jí)線程M開(kāi)始運(yùn)行它不需要資源S。由于M的優(yōu)先級(jí)高于L它會(huì)搶占L的CPU。結(jié)果優(yōu)先級(jí)反轉(zhuǎn)發(fā)生了。高優(yōu)先級(jí)的H在等待低優(yōu)先級(jí)的L而L卻無(wú)法運(yùn)行因?yàn)楸籑搶占導(dǎo)致H實(shí)際上在等待一個(gè)中優(yōu)先級(jí)的線程M。H的響應(yīng)時(shí)間無(wú)法得到保證。解決方案優(yōu)先級(jí)繼承協(xié)議當(dāng)高優(yōu)先級(jí)線程等待低優(yōu)先級(jí)線程持有的鎖時(shí)臨時(shí)將低優(yōu)先級(jí)線程的優(yōu)先級(jí)提升到與高優(yōu)先級(jí)線程相同使其能盡快執(zhí)行完并釋放鎖從而讓高優(yōu)先級(jí)線程繼續(xù)。許多現(xiàn)代實(shí)時(shí)操作系統(tǒng)如VxWorks, QNX的互斥鎖實(shí)現(xiàn)了此協(xié)議。優(yōu)先級(jí)天花板協(xié)議為信號(hào)量鎖預(yù)設(shè)一個(gè)優(yōu)先級(jí)天花板通常高于所有可能使用該鎖的線程的優(yōu)先級(jí)。任何線程一旦獲得該鎖其優(yōu)先級(jí)立即提升到天花板優(yōu)先級(jí)直到釋放鎖。這可以防止中間優(yōu)先級(jí)的線程插隊(duì)。使用無(wú)鎖數(shù)據(jù)結(jié)構(gòu)或讀寫(xiě)鎖從根本上避免互斥或使用更細(xì)粒度的鎖。4.4 信號(hào)量與條件變量的區(qū)別與選用初學(xué)者?;煜盘?hào)量和條件變量Condition Variable。兩者都用于線程同步但有本質(zhì)區(qū)別特性信號(hào)量 (Semaphore)條件變量 (Condition Variable)狀態(tài)持有有狀態(tài)。信號(hào)量本身維護(hù)一個(gè)計(jì)數(shù)值。無(wú)狀態(tài)。它本身不存儲(chǔ)任何條件狀態(tài)只是一個(gè)等待隊(duì)列。操作關(guān)聯(lián)P/V操作是自足的直接改變狀態(tài)并觸發(fā)喚醒。wait操作必須與一個(gè)共享變量的謂詞檢查Predicate結(jié)合使用且必須與一個(gè)互斥鎖Mutex配合。喚醒機(jī)制V操作總是會(huì)增加計(jì)數(shù)值并可能喚醒一個(gè)/所有等待者。signal/broadcast操作只是通知不改變?nèi)魏螤顟B(tài)。等待線程被喚醒后必須重新檢查條件。主要用途更通用可直接用于互斥、同步以及資源計(jì)數(shù)。專門(mén)用于等待某個(gè)復(fù)雜的條件成立通常用于“等待-通知”模式如線程池、阻塞隊(duì)列。如何選擇當(dāng)你需要管理一個(gè)明確的、可數(shù)的資源池如連接池、內(nèi)存塊、令牌時(shí)用計(jì)數(shù)信號(hào)量。當(dāng)你只需要簡(jiǎn)單的互斥且不需要優(yōu)先級(jí)繼承等高級(jí)特性時(shí)可以用二元信號(hào)量但更推薦用專門(mén)的互斥鎖。當(dāng)你需要等待一個(gè)復(fù)雜的、基于多個(gè)共享變量變化的條件時(shí)例如“緩沖區(qū)非空且寫(xiě)鎖可用”必須使用條件變量互斥鎖謂詞檢查的模式。絕對(duì)不要試圖用信號(hào)量來(lái)模擬條件變量極易出錯(cuò)。5. 實(shí)戰(zhàn)演練手寫(xiě)一個(gè)簡(jiǎn)單的信號(hào)量模擬器為了加深理解我們可以在用戶態(tài)利用更基礎(chǔ)的原子操作和線程庫(kù)模擬一個(gè)簡(jiǎn)單的信號(hào)量。這里以C和std::atomic、std::mutex、std::condition_variable為例。注意這是一個(gè)教學(xué)模型實(shí)際生產(chǎn)環(huán)境應(yīng)使用系統(tǒng)原生的信號(hào)量如sem_t。#include atomic #include mutex #include condition_variable #include queue class SimpleSemaphore { private: int count_; std::mutex mutex_; std::condition_variable cv_; // 用于實(shí)現(xiàn)等待隊(duì)列 // 注意真實(shí)的信號(hào)量等待隊(duì)列是FIFO這里用condition_variable模擬其喚醒順序可能不確定。 public: explicit SimpleSemaphore(int initial 0) : count_(initial) {} void P() { // acquire, wait std::unique_lockstd::mutex lock(mutex_); // 必須使用while循環(huán)防止虛假喚醒 while (count_ 0) { cv_.wait(lock); // 釋放mutex_并等待被喚醒后重新獲取mutex_ } --count_; // lock析構(gòu)時(shí)自動(dòng)釋放mutex_ } bool tryP() { // non-blocking acquire std::unique_lockstd::mutex lock(mutex_); if (count_ 0) { --count_; return true; } return false; } void V() { // release, signal std::unique_lockstd::mutex lock(mutex_); count_; cv_.notify_one(); // 喚醒一個(gè)等待的線程 // lock析構(gòu)時(shí)自動(dòng)釋放mutex_ } int getCount() const { // 注意此操作不是原子的僅用于調(diào)試不能用于同步邏輯判斷。 return count_.load(); } };對(duì)這個(gè)模擬器的解析與注意事項(xiàng)while (count_ 0)而不是if這是使用條件變量的鐵律。condition_variable可能存在“虛假喚醒”spurious wakeup即線程在沒(méi)有收到notify的情況下也可能從wait中返回。因此被喚醒后必須重新檢查條件是否真正滿足。性能這個(gè)模擬器在P和V操作時(shí)都需要獲取一個(gè)互斥鎖mutex_在高并發(fā)爭(zhēng)搶場(chǎng)景下這個(gè)鎖可能成為性能瓶頸。操作系統(tǒng)內(nèi)核實(shí)現(xiàn)的信號(hào)量通常有更優(yōu)化的實(shí)現(xiàn)。公平性std::condition_variable不保證嚴(yán)格的FIFO喚醒順序而一些實(shí)時(shí)系統(tǒng)對(duì)信號(hào)量的公平性有要求。原子性我們使用mutex_來(lái)保證count_的修改和條件檢查的原子性。真實(shí)的信號(hào)量實(shí)現(xiàn)可能使用CPU的原子指令如compare_and_swap在用戶態(tài)實(shí)現(xiàn)無(wú)鎖或輕量級(jí)鎖性能更高。6. 高級(jí)話題信號(hào)量的變體與在現(xiàn)代并發(fā)庫(kù)中的角色6.1 讀寫(xiě)鎖信號(hào)量思想的延伸讀寫(xiě)鎖Read-Write Lock是一種特殊的同步原語(yǔ)它允許多個(gè)讀者同時(shí)訪問(wèn)共享資源但只允許一個(gè)寫(xiě)者訪問(wèn)且讀者與寫(xiě)者互斥。這可以顯著提高讀多寫(xiě)少場(chǎng)景的性能。我們可以用兩個(gè)二元信號(hào)量和一個(gè)整數(shù)讀者計(jì)數(shù)器來(lái)實(shí)現(xiàn)一個(gè)讀寫(xiě)鎖一個(gè)信號(hào)量wrt初始為1用于控制寫(xiě)互斥。一個(gè)信號(hào)量mutex初始為1用于保護(hù)讀者計(jì)數(shù)器readcount的更新。當(dāng)readcount從0變?yōu)?時(shí)讀者需要P(wrt)當(dāng)readcount從1變?yōu)?時(shí)讀者需要V(wrt)。寫(xiě)者則在寫(xiě)操作前后執(zhí)行P(wrt)和V(wrt)。當(dāng)然現(xiàn)代系統(tǒng)都提供了原生的讀寫(xiě)鎖如pthread_rwlock_t它們解決了“寫(xiě)者饑餓”等問(wèn)題實(shí)現(xiàn)也更高效。6.2 屏障另一種同步原語(yǔ)屏障Barrier用于讓一組線程在某個(gè)執(zhí)行點(diǎn)同步所有線程都到達(dá)屏障點(diǎn)后才能一起繼續(xù)執(zhí)行。這也可以用信號(hào)量來(lái)實(shí)現(xiàn)但實(shí)現(xiàn)起來(lái)比互斥和生產(chǎn)者-消費(fèi)者更復(fù)雜一些通常需要配合一個(gè)計(jì)數(shù)器和一個(gè)“代”generation的概念來(lái)防止復(fù)用錯(cuò)誤。在實(shí)際中我們直接使用系統(tǒng)提供的屏障原語(yǔ)如pthread_barrier_t。6.3 在現(xiàn)代語(yǔ)言并發(fā)庫(kù)中的地位在Java、C#、Go、Python等高級(jí)語(yǔ)言中信號(hào)量作為底層原語(yǔ)依然存在如java.util.concurrent.Semaphore,System.Threading.SemaphoreSlim但開(kāi)發(fā)者更常接觸的是更高層次的抽象Javasynchronized關(guān)鍵字、ReentrantLock、CountDownLatch、CyclicBarrier、BlockingQueue等它們底層可能使用了信號(hào)量的思想但提供了更安全、更易用的接口。Go通過(guò)channel和select語(yǔ)句“不要通過(guò)共享內(nèi)存來(lái)通信而應(yīng)通過(guò)通信來(lái)共享內(nèi)存”其channel的緩沖特性本質(zhì)上就是一種信號(hào)量模式。Pythonthreading.Semaphore但在asyncio異步編程中更強(qiáng)調(diào)單線程內(nèi)的協(xié)程協(xié)作使用asyncio.Semaphore、asyncio.Queue等。核心建議作為開(kāi)發(fā)者理解信號(hào)量的原理是構(gòu)建堅(jiān)實(shí)并發(fā)知識(shí)體系的基石。但在具體項(xiàng)目中優(yōu)先選擇語(yǔ)言或框架推薦的高級(jí)并發(fā)工具它們通常更安全、更不易出錯(cuò)并且經(jīng)過(guò)了充分的測(cè)試和優(yōu)化。當(dāng)你遇到這些高級(jí)工具無(wú)法解決的、非常底層的同步問(wèn)題時(shí)再考慮直接使用信號(hào)量。