c# volatile 關(guān)鍵字的拾遺補(bǔ)漏
要理解 C# 中的 volatile
關(guān)鍵字,就要先知道編譯器背后的一個(gè)基本優(yōu)化原理。比如對于下面這段代碼:
public class Example { public int x; public void DoWork() { x = 5; var y = x + 10; Debug.WriteLine("x = " +x + ", y = " +y); } }
在 Release 模式下,編譯器讀取 x = 5
后緊接著讀取 y = x + 10
,在單線程思維模式下,編譯器會(huì)認(rèn)為 y
的值始終都是 15
。所以編譯器會(huì)把 y = x + 10
優(yōu)化為 y = 15
,避免每次讀取 y
都執(zhí)行一次 x + 5
。但 x
字段的值可能在運(yùn)行時(shí)被其它的線程修改,我們拿到的 y
值并不是通過最新修改的 x
計(jì)算得來的,y
的值永遠(yuǎn)都是 15
。
也就是說,編譯器在 Release 模式下會(huì)對字段的訪問進(jìn)行優(yōu)化,它假定字段都是由單個(gè)線程訪問的,把與該字段相關(guān)的表達(dá)式運(yùn)算結(jié)果編譯成常量緩存起來,避免每次訪問都重復(fù)運(yùn)算。但這樣就可能導(dǎo)致其它線程修改了字段值而當(dāng)前線程卻讀取不到最新的字段值。為了防止編譯器這么做,你就要讓編譯器用多線程思維去解讀代碼。告訴編譯器字段的值可能會(huì)被其它線程修改,這種情況不要使用優(yōu)化策略。而要做到這一點(diǎn),就需要使用 volatile
關(guān)鍵字。
給類的字段添加 volatile
關(guān)鍵字,目的是告訴編譯器該字段的值可能會(huì)被多個(gè)獨(dú)立的線程改變,不要對該字段的訪問進(jìn)行優(yōu)化。
使用 volatile
可以確保字段的值是可用的最新值,而且該值不會(huì)像非 volatile
字段值那樣受到緩存的影響。好的做法是將每個(gè)可能被多個(gè)線程使用的字段標(biāo)記為 volatile
,以防止非預(yù)期的優(yōu)化行為。
為了加深理解,我們來看一個(gè)實(shí)際的例子:
public class Worker { private bool _shouldStop; public void DoWork() { bool work = false; // 注意:這里會(huì)被編譯器優(yōu)化為 while(true) while (!_shouldStop) { work = !work; // do sth. } Console.WriteLine("工作線程:正在終止..."); } public void RequestStop() { _shouldStop = true; } } public class Program { public static void Main() { var worker = new Worker(); Console.WriteLine("主線程:啟動(dòng)工作線程..."); var workerTask = Task.Run(worker.DoWork); // 等待 500 毫秒以確保工作線程已在執(zhí)行 Thread.Sleep(500); Console.WriteLine("主線程:請求終止工作線程..."); worker.RequestStop(); // 待待工作線程執(zhí)行結(jié)束 workerTask.Wait(); //workerThread.Join(); Console.WriteLine("主線程:工作線程已終止"); } }
在這個(gè)例子中,while (!_shouldStop)
會(huì)被編譯器優(yōu)化為 while(true)
。我們可以看一下實(shí)際的運(yùn)行效果來驗(yàn)證這一點(diǎn)。切換 Release 模式,按 Ctrl + F5 運(yùn)行程序,運(yùn)行效果始終如下:
程序運(yùn)行后,雖然主線程在 500 毫秒后執(zhí)行 RequestStop()
方法修改了 _shouldStop
的值,但工作線程始終都獲取不到 _shouldStop
最新的值,也就永遠(yuǎn)都不會(huì)終止 while
循環(huán)。
我們修改一下程序,對 _shouldStop
字段加上 volatile
關(guān)鍵字:
public class Worker { private volatile bool _shouldStop; public void DoWork() { bool work = false; // 獲取的是最新的 _shouldStop 值 while (!_shouldStop) { work = !work; // do sth. } Console.WriteLine("工作線程:正在終止..."); } // ...(略) }
此時(shí)在主線程調(diào)用 RequestStop()
方法后,工作線程便立即終止了,運(yùn)行效果如下圖所示:
這說明加了 volatile
關(guān)鍵字后,程序可以實(shí)時(shí)讀取到字段的最新值。
注意,一定要切換為 Release 模式運(yùn)行才能看到 volatile
發(fā)揮的作用,Debug 模式下即使添加了 volatile
關(guān)鍵字,編譯器也是不會(huì)執(zhí)行優(yōu)化的。
當(dāng)然,并不是所有的類型都可以使用 volatile
關(guān)鍵字修飾的,常見的使用 volatile
的類型是這些簡單類型:sbyte, byte, short, ushort, int, uint, char, float 和 bool,其它的請查看參考鏈接。
作者:精致碼農(nóng)
聯(lián)系:liam.wang@live.com
以上就是c# volatile 關(guān)鍵字的拾遺補(bǔ)漏的詳細(xì)內(nèi)容,更多關(guān)于c# volatile 關(guān)鍵字的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#實(shí)現(xiàn)漂亮的數(shù)字時(shí)鐘效果
這篇文章主要介紹了C#實(shí)現(xiàn)漂亮的數(shù)字時(shí)鐘效果,涉及時(shí)間函數(shù)的應(yīng)用及繪圖的方法,需要的朋友可以參考下2014-10-10c#根據(jù)文件大小顯示文件復(fù)制進(jìn)度條實(shí)例
這篇文章主要介紹了c#根據(jù)文件大小顯示文件復(fù)制進(jìn)度條實(shí)例,有需要的朋友可以參考一下2013-12-12C#算法函數(shù):獲取一個(gè)字符串中的最大長度的數(shù)字
這篇文章介紹了使用C#獲取一個(gè)字符串中最大長度的數(shù)字的實(shí)例代碼,有需要的朋友可以參考一下。2016-06-06C#實(shí)現(xiàn)打開畫圖的同時(shí)載入圖片、最大化顯示畫圖窗體的方法
這篇文章主要介紹了C#實(shí)現(xiàn)打開畫圖的同時(shí)載入圖片、最大化顯示畫圖窗體的方法,涉及C#針對窗體及圖片操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08C#如何將DataTable導(dǎo)出到Excel解決方案
由于公司項(xiàng)目中需要將系統(tǒng)內(nèi)用戶操作的所有日志進(jìn)行轉(zhuǎn)存?zhèn)浞?,考慮到以后可能還需要還原,所以最后決定將日志數(shù)據(jù)備份到Excel中2012-11-11Unity游戲開發(fā)之射擊小游戲的實(shí)現(xiàn)
本篇文章為大家?guī)硪粋€(gè)橫版2D射擊小游戲,游戲制作超級簡單,玩法一學(xué)就會(huì)。文中的示例代碼講解詳細(xì),快跟隨小編一起動(dòng)手試一試2022-03-03