<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          使用條件變量的坑你知道嗎

          共 785字,需瀏覽 2分鐘

           ·

          2020-08-30 01:46


          【時(shí)間管理的本質(zhì)是目的管理,如果我們想要更好的利用時(shí)間,最先要做的是找到最值得我們花費(fèi)時(shí)間的事情,自我學(xué)習(xí)和精進(jìn)才是最值得我們做的有意義的事。】


          ——《認(rèn)知破局》


          想必大家開發(fā)過程中都會(huì)用到多線程,用到多線程基本上都會(huì)用到條件變量,你理解的條件變量只是簡(jiǎn)單的wait和notify嗎,最近工作中看同事也都只是簡(jiǎn)單的使用wait和notify,導(dǎo)致項(xiàng)目出現(xiàn)bug卻不知如何fix bug,其實(shí)這里面還是有一些坑的,程序喵這里總結(jié)給大家。

          本文內(nèi)容簡(jiǎn)介:


          • 什么是條件變量?

          • 條件變量如何使用?

          • 如何解決條件變量的信號(hào)丟失問題?

          • 如何解決條件變量的虛假喚醒問題?

          • 條件變量為什么一定要和鎖配合使用?



          1


          什么是條件變量?


          條件變量是多線程程序中用來實(shí)現(xiàn)等待和喚醒邏輯常用的方法。通常有wait和notify兩個(gè)動(dòng)作,wait用于阻塞掛起線程A,直到另一個(gè)線程B通過通過notify喚醒線程A,喚醒后線程A會(huì)繼續(xù)運(yùn)行。

          條件變量在多線程中很常用,在有名的生產(chǎn)者和消費(fèi)者問題中,消費(fèi)者如何知道生成者是否生產(chǎn)出了可以消費(fèi)的產(chǎn)品,通過while循環(huán)不停的去判斷是否有可消費(fèi)的產(chǎn)品?眾所周知,死循環(huán)極其消耗CPU性能,所以需要使用條件變量來阻塞線程,降低CPU占用率。


          2


          條件變量的使用

          拿生產(chǎn)者和消費(fèi)者問題舉例,看下面這段代碼:

          std::mutex mutex;std::condition_variable cv;std::vector<int> vec;
          void Consume() { std::unique_lock<std::mutex> lock(mutex); cv.wait(lock); std::cout << "consume " << vec.size() << "\n";}
          void Produce() { std::unique_lock<std::mutex> lock(mutex); vec.push_back(1); cv.notify_all(); std::cout << "produce \n";}
          int main() { std::thread t(Consume); t.detach(); Produce(); return 0;}

          本意是消費(fèi)者線程阻塞,等待生產(chǎn)者生產(chǎn)數(shù)據(jù)后去通知消費(fèi)者線程,這樣消費(fèi)者線程就可以拿到數(shù)據(jù)去消費(fèi)。

          但這里有個(gè)問題:

          如果先執(zhí)行的Produce(),后執(zhí)行的Consume(),生產(chǎn)者提前生產(chǎn)出了數(shù)據(jù),去通知消費(fèi)者,但是此時(shí)消費(fèi)者線程如果還沒有執(zhí)行到wait語句,即線程還沒有處于掛起等待狀態(tài),線程沒有等待此條件變量上,那通知的信號(hào)就丟失了,后面Consume()中才執(zhí)行wait處于等待狀態(tài),但此時(shí)生產(chǎn)者已經(jīng)不會(huì)再觸發(fā)notify,那消費(fèi)者線程就會(huì)始終阻塞下去,出現(xiàn)bug。

          如何解決這個(gè)問題呢?可以附加一個(gè)判斷條件,就可以解決這種信號(hào)丟失問題,見代碼:

          std::mutex mutex;std::condition_variable cv;std::vector<int> vec;
          void Consumer() { std::unique_lock<std::mutex> lock(mutex); if (vec.empty()) { // 加入此判斷條件 cv.wait(lock); } std::cout << "consumer " << vec.size() << "\n";}
          void Produce() { std::unique_lock<std::mutex> lock(mutex); vec.push_back(1); cv.notify_all(); std::cout << "produce \n";}
          int main() { std::thread t(Consumer); t.detach(); Produce(); return 0;}

          通過增加附加條件可以解決信號(hào)丟失的問題,但這里還有個(gè)地方需要注意,消費(fèi)者線程處于wait阻塞狀態(tài)時(shí),即使沒有調(diào)用notify,操作系統(tǒng)也會(huì)有一些概率會(huì)喚醒處于阻塞的線程,使其繼續(xù)執(zhí)行下去,這就是虛假喚醒問題,當(dāng)出現(xiàn)了虛假喚醒后,消費(fèi)者線程繼續(xù)執(zhí)行,還是沒有可以消費(fèi)的數(shù)據(jù),出現(xiàn)了bug。?

          那怎么解決虛假喚醒的問題呢,可以在線程由阻塞狀態(tài)被喚醒后繼續(xù)判斷附加條件,看是否滿足喚醒的條件,如果滿足則繼續(xù)執(zhí)行,如果不滿足,則繼續(xù)去等待,體現(xiàn)在代碼中,即將if判斷改為while循環(huán)判斷,見代碼:

          std::mutex mutex;std::condition_variable cv;std::vector<int> vec;
          void Consumer() { std::unique_lock<std::mutex> lock(mutex); while (vec.empty()) { // 將if改為while cv.wait(lock); } std::cout << "consumer " << vec.size() << "\n";}
          void Produce() { std::unique_lock<std::mutex> lock(mutex); vec.push_back(1); cv.notify_all(); std::cout << "produce \n";}
          int main() { std::thread t(Consumer); t.detach(); Produce(); return 0;}

          看到這里相信你已經(jīng)明白條件變量的使用啦,需要使用while循環(huán)附加判斷條件來解決條件變量的信號(hào)丟失和虛假喚醒問題。



          3


          有沒有更簡(jiǎn)單的“避坑”方式


          難道我們每次都必須要使用while循環(huán)和附加條件來操作條件變量嗎?這豈不是很麻煩?

          NO!


          在C++中其實(shí)有更好的封裝,只需要調(diào)用wait函數(shù)時(shí),在參數(shù)中直接添加附加條件就好了,內(nèi)部已經(jīng)做好了while循環(huán)判斷,直接使用即可,見代碼:

          std::mutex mutex;std::condition_variable cv;std::vector<int> vec;
          void Consumer() { std::unique_lock<std::mutex> lock(mutex); cv.wait(lock, [&](){ return !vec.empty(); }); // 這里可以直接使用C++的封裝 std::cout << "consumer " << vec.size() << "\n";}
          void Produce() { std::unique_lock<std::mutex> lock(mutex); vec.push_back(1); cv.notify_all(); std::cout << "produce \n";}
          int main() { std::thread t(Consumer); t.detach(); Produce(); return 0;}

          但在C語言中就沒辦法啦,大家只能自己做一層封裝啦。




          4


          為什么條件變量需要和鎖配合使用?


          為什么叫條件變量呢?

          因?yàn)閮?nèi)部是通過判斷及修改某個(gè)全局變量來決定線程的阻塞與喚醒,多線程操作同一個(gè)變量肯定需要加鎖來使得線程安全。同時(shí),一個(gè)簡(jiǎn)單的wait函數(shù)調(diào)用內(nèi)部會(huì)很復(fù)雜的,有可能線程A調(diào)用了wait函數(shù)但是還沒有進(jìn)入到wait阻塞等待前,另一個(gè)線程B在此時(shí)卻調(diào)用了notify函數(shù),此時(shí)nofity的信號(hào)就丟失啦,如果加了鎖,線程B必須等待線程A釋放了鎖并進(jìn)入了等待狀態(tài)后才可以調(diào)用notify,繼而防止信號(hào)丟失。

          關(guān)于條件變量就介紹到這里,希望大家能有所收獲,平時(shí)使用過程中可以避掉條件變量的坑。

          瀏覽 36
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产三级网站免费观看 | 最新无码国产精品色在线看 | 韩国精品无码 | 逼逼爱插插网站 | 伊人大香蕉在线狼人 |