本文的參考,來(lái)自于文章http://blogs.msdn.com/b/oldnewthing/archive/2004/09/15/229915.aspx。
成都創(chuàng)新互聯(lián)成都企業(yè)網(wǎng)站建設(shè)服務(wù),提供網(wǎng)站建設(shè)、做網(wǎng)站網(wǎng)站開(kāi)發(fā),網(wǎng)站定制,建網(wǎng)站,網(wǎng)站搭建,網(wǎng)站設(shè)計(jì),響應(yīng)式網(wǎng)站,網(wǎng)頁(yè)設(shè)計(jì)師打造企業(yè)風(fēng)格網(wǎng)站,提供周到的售前咨詢和貼心的售后服務(wù)。歡迎咨詢做網(wǎng)站需要多少錢(qián):18982081108Interlocked類MSDN中對(duì)他的定義為:為變量在多線程共享的情況下提供原子操作。
很多人對(duì)于Interlocked的使用,僅限于Interlocked.Increment方法,這個(gè)方法在多線程環(huán)境下,總可以保證變量自增的正確性。
那么原子方法的定義是什么呢?顧名思義,原子一般認(rèn)為是不可再分的,所以原子方法就是不可再分的方法,即在一個(gè)原子操作中,處理器能夠在一個(gè)指令傳輸中完成讀值和寫(xiě)值, 也就是說(shuō),在原子操作完成之前,任何IO機(jī)制或處理器都不能對(duì)這個(gè)內(nèi)存進(jìn)行讀寫(xiě)操作。原子操作常常被使用于大多數(shù)操作系統(tǒng)的內(nèi)核和基礎(chǔ)類庫(kù)中,而且大多數(shù)的計(jì)算機(jī)硬件,編譯器和類庫(kù)都支持了各個(gè)層面上的原子操作。
很多人認(rèn)為使用原子操作能夠保證多線程下的數(shù)據(jù)的正確性。那么看下面的代碼:
class Program { static private object _obj = new object(); static void Main(string[] args) { int count = 0; for (int i = 0; i < 10000; i++) { int x = 2; Task t1 = Task.Factory.StartNew(() => { InterlockedMultiply(ref x, 10); }); Task t2 = Task.Factory.StartNew(() => { Interlocked.Increment(ref x); }); Task.WaitAll(t1, t2); if (x != 21 && x != 30) { Console.WriteLine(x); count++; } } Console.WriteLine("值不為21且不為30的次數(shù):"+count); Console.WriteLine("done!"); } static void InterlockedMultiply(ref int x, int y) { lock (_obj) { x = x * y; } } }
上述代碼中,循環(huán)體中,開(kāi)2個(gè)task,可以認(rèn)為是2個(gè)線程,這2個(gè)線程運(yùn)行2個(gè)方法,一個(gè)是原子方法,另一個(gè)是加了鎖的方法。
先用常規(guī)的思路分析一下:
由于t2是原子操作,所以保證x的值可以被自增,不會(huì)受其他線程的干擾,t1中運(yùn)行的方法是InterlockedMultiply,并加入lock。
當(dāng)方法運(yùn)行的時(shí)候,要么是t1先運(yùn)行,要么是t2先運(yùn)行,所以,要么值為(2+1)*10=30,要么值為(2*10)+1=21。
但是當(dāng)運(yùn)行的時(shí)候,循環(huán)10000次后,結(jié)果為:
值不為21且不為30的次數(shù):103。
導(dǎo)致這種問(wèn)題的原因,在于InterlockedMultiply的實(shí)現(xiàn),看下面的細(xì)節(jié)圖
當(dāng)t2在讀取完x的后,把該值讀到寄存器中,寄存器中的值為2了,而t1中的原子操作對(duì)x做了自增,也就是說(shuō)此時(shí)x是3了,接著,t2線程在寄存器中把該值乘以10后,寫(xiě)入到內(nèi)存,然后下一個(gè)時(shí)刻釋放鎖,此時(shí)就把之前的值有覆蓋成20了。
也就是說(shuō)雖然2個(gè)線程中的代碼都是各自獨(dú)立的,但是在并發(fā)情況下,還是不能保證值的正確性。原因如下:
1.由于我們模擬的InterlockedMultiply方法中的操作,并不具備原子性。雖然加了lock,但是對(duì)于那些無(wú)需獲取鎖就能訪問(wèn)x的函數(shù)體來(lái)說(shuō),它們是可以直接修改x的。也就是說(shuō),這里加了lock也沒(méi)什么用。
2.Interlocked.Increment能夠保證的是該操作是原子性的,在它將值設(shè)為3的期間,不會(huì)有其他操作讀寫(xiě)x。(這點(diǎn)很重要,如果這點(diǎn)不能保證,那么上面的情況中會(huì)出現(xiàn):
假設(shè)原本step6,step7中的操作在step5期間進(jìn)行,當(dāng)t2的操作快于t1先完成時(shí),那么X的值先被設(shè)成了20,然后又立刻被t1設(shè)成了3,這就導(dǎo)致了值會(huì)是3的情況出現(xiàn)。)
3.雖然Interlocked.Increment使得x的自增具備原子性,但它不能夠保證的是:但它不能夠保證的是:其他線程在操作x時(shí),拿到的x的值是最新的。
基于上面的分析,可以得出一個(gè)結(jié)論,多線程下的并發(fā)編程,需要考慮的更周到,原子操作并不保證多線程環(huán)境下的值總是對(duì)的。
如果要實(shí)現(xiàn)上面的InterlockedMultiply方法,參考文檔中的寫(xiě)法是采用了do while的循環(huán),這個(gè)操作就使得t2線程在那邊不斷的重試,其實(shí)也不是最好的辦法。我能想到的辦法是把t2中也加鎖:
Task t2 = Task.Factory.StartNew(() => { lock (_obj) { Interlocked.Increment(ref x); } });
但是這種辦法也不好,使得線程不能正真的同時(shí)運(yùn)行,總有一個(gè)陷于等待中。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。