<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>

          C語(yǔ)言中的volatile到底有什么用?

          共 2603字,需瀏覽 6分鐘

           ·

          2022-08-08 16:50

          學(xué)C語(yǔ)言時(shí)有一個(gè)奇怪的關(guān)鍵字volatile,這到底有什么用呢?


          volatile與編譯器
          首先來(lái)看這樣一段代碼:
          int busy = 1;
          void wait() { while(busy) { ; }}
          編譯一下,注意,這里使用O2優(yōu)化
          讓我們仔細(xì)看看生成的這段匯編:
          wait: mov eax, DWORD PTR busy[rip].L2: test eax, eax jne .L2 retbusy: .long 1
          其中L2這一段即為while循環(huán),這段指令是經(jīng)過(guò)編譯器優(yōu)化的,可以看到,決定能否跳出循環(huán)是通過(guò)檢查寄存器eax來(lái)完成的,而沒(méi)有檢查變量busy所在內(nèi)存的真實(shí)內(nèi)容。
          注意,對(duì)于這段代碼來(lái)說(shuō)這里的優(yōu)化是正確的,但問(wèn)題是如果還有其它代碼修改了變量busy,那么這里的優(yōu)化會(huì)導(dǎo)致其它代碼對(duì)變量busy的修改根本就不能生效,就像這樣:
          int busy = 1;
          // 該函數(shù)在A線(xiàn)程中執(zhí)行void wait() { while(busy) { ; }}
          // 該函數(shù)在B線(xiàn)程中執(zhí)行void signal() { busy = 0;}
          如果wait函數(shù)中while循環(huán)對(duì)應(yīng)的機(jī)器指令僅僅從寄存器中讀取數(shù)據(jù)那么即使B線(xiàn)程的signal函數(shù)修改了busy變量也不能讓wait函數(shù)從循環(huán)中跳出來(lái)。
          如果你對(duì)busy變量使用volatile修飾,生成的指令就變成這樣了:
          wait:.L2: mov eax, DWORD PTR busy[rip] test eax, eax jne .L2 retbusy: .long 1
          注意看此時(shí)L2這一段,每次都從busy變量所在的內(nèi)存中讀取數(shù)據(jù)并存放在eax,然后再去判斷,這樣就能確保每次都能讀取到busy變量的最新值。
          實(shí)際上你可以把寄存器eax當(dāng)做busy所在內(nèi)存的cache,當(dāng)cache(寄存器)和內(nèi)存中的數(shù)據(jù)一致時(shí)不會(huì)有任何問(wèn)題,但當(dāng)cache與內(nèi)存中的數(shù)據(jù)不一致時(shí)(也就是內(nèi)存已被更新但cache保存的還是舊數(shù)據(jù)),程序的運(yùn)行往往出乎預(yù)料。
          除了多線(xiàn)程的例子,還有一類(lèi)就是signal handler以及硬件修改該變量(用C語(yǔ)言與硬件交互式時(shí)經(jīng)常遇到),如果編譯器生成文章開(kāi)頭那樣的指令那么等待線(xiàn)程將檢測(cè)不到signal handler或者硬件對(duì)變量的修改。
          因此在這里我們需要告訴編譯器:“不要耍小聰明,不要只從寄存器中讀數(shù)據(jù),這個(gè)變量可能在其它地方已經(jīng)被修改了,使用時(shí)從內(nèi)存中獲取最新數(shù)據(jù)”。
          現(xiàn)在是時(shí)候簡(jiǎn)單總結(jié)一下了,volatile僅僅阻止編譯器試圖去優(yōu)化對(duì)變量的讀取操作

          volatile與多線(xiàn)程
          一定要注意volatile僅僅確保變量的可見(jiàn)性,但和變量的原子訪(fǎng)問(wèn)沒(méi)有半毛錢(qián)關(guān)系,這是兩個(gè)完全不同的任務(wù)
          假設(shè)有一個(gè)非常復(fù)雜的結(jié)構(gòu)體struct foo:
          struct data { int a; int b; int c; ...};
          volatile struct data foo;
          void thread1() { foo.a = 1; foo.b = 2; foo.c = 3; ...}
          void thread2() { int a = foo.a; int b = foo.b; int c = foo.c; ...}
          你僅僅用volatile去修飾變量foo只是確保了當(dāng)該變量被thread1修改后我們能在thread2中讀取到最新值,但是這解決不了多線(xiàn)程并發(fā)讀寫(xiě)需要原子訪(fǎng)問(wèn)foo的問(wèn)題
          確保變量原子性訪(fǎng)問(wèn)一般都采用鎖,當(dāng)使用鎖時(shí),鎖本身就包含了volatile提供能力,即,確保變量的可見(jiàn)性,因此當(dāng)使用鎖時(shí)沒(méi)有必要使用volatile。

          volatile與memory order
          有的同學(xué)可能會(huì)想如果我想用volatile修飾的變量沒(méi)有那么復(fù)雜,僅僅是一個(gè)int,就像這樣:
          volatile int busy = 0;
          A線(xiàn)程讀取busy變量,B線(xiàn)程更新busy變量,當(dāng)A檢測(cè)到busy變化后執(zhí)行特定操作,這樣可行嗎?既然通過(guò)volatile修飾后可以確保每次都從內(nèi)存中讀取busy,那么應(yīng)該可以這樣使用吧。
          然而,計(jì)算機(jī)在概念上可能相對(duì)簡(jiǎn)單些,但在工程實(shí)踐中是復(fù)雜的。
          我們知道由于CPU與內(nèi)存之間的速度差異非常大,CPU與內(nèi)存之間有一層cache,CPU其實(shí)并沒(méi)有直接讀取內(nèi)存,cache的存在會(huì)讓問(wèn)題復(fù)雜起來(lái),限于篇幅與本文主題這里不再展開(kāi)。
          為優(yōu)化內(nèi)存讀寫(xiě),CPU可能會(huì)對(duì)內(nèi)存讀寫(xiě)操作進(jìn)行指令重排,reordering,帶來(lái)的后果就是:假設(shè)在線(xiàn)程1中先后執(zhí)行第N行代碼與第N+1行代碼,但在線(xiàn)程2看來(lái)卻是第N+1行代碼先生效,假設(shè)X的初始值為0,Y的初始值為1:
          線(xiàn)程1           線(xiàn)程2X = 10         if (!busy)busy = 0;         Y = X;
          當(dāng)線(xiàn)程2檢測(cè)到busy為0后讀取X的值,此時(shí)讀取到的X值可能為0。
          為解決這一問(wèn)題,我們需要的不是volatile,volatile解決不了reordering問(wèn)題,我們需要的是內(nèi)存屏障,memory barrier。
          內(nèi)存屏障是一類(lèi)機(jī)器指令,該指令對(duì)處理器在該屏障指令之前與之后的內(nèi)存操作進(jìn)行了限制,確保不會(huì)出現(xiàn)重排問(wèn)題。
          而內(nèi)存屏障帶來(lái)的效果依然能夠涵蓋volatile提供的功能,因此也不需要volatile。
          可以看到,在多線(xiàn)程環(huán)境下我們幾乎總是不會(huì)使用volatile關(guān)鍵字。
          好啦,這個(gè)話(huà)題就到這里,如果我有理解不到位的地方歡迎大家批評(píng)指正。
          w3cschool編程獅
          專(zhuān)門(mén)學(xué)習(xí)編程的網(wǎng)站

          編程獅-前端交流群在構(gòu)建中

          正在學(xué)習(xí)前端或者準(zhǔn)備學(xué)習(xí)前端的小伙伴

          都可以來(lái)加入我們

          群里可以進(jìn)行學(xué)習(xí)討論、八卦閑談

          后面還將會(huì)派送福利哦~

          想要加入的小伙伴

          可以聯(lián)系我們的學(xué)習(xí)顧問(wèn)-七七 ↑

          瀏覽 33
          點(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>
                  三级操逼片 | 黄色片A片| 日本成人精品免费在线视频 | 国产乱婬AV公 | 日本A片在线播放 |