聊聊Interlocked.CompareExchange吧?
Interlocked.CompareExchange這玩意用法被講的很爛了,為何再次被提及,主要看了下官網(wǎng)對其使用描述可能對于部分童鞋來講具備一定迷惑性,需我們進(jìn)一步消化下,話不多講,我們來看看吧~~~
淺談如標(biāo)題
關(guān)于啥時(shí)候用此語法,這.....此處省略若干字,上代碼
- var location = 1;
- var value = 3;
- var compared = 1;
- Interlocked.CompareExchange(ref location, value, compared);
若原始值(location)與比較值(compared)一致,則將當(dāng)前值(value)替換原始值。如圖上述,此時(shí)原始值將為3,毫無疑問,我們很清楚這是基本用法
問題來了,該語法既可比較基本類型也可比較引用對象
當(dāng)比較對象時(shí),官方還特此備注注意:
比較對象的引用相等性,而不是值相等性。結(jié)果,兩個(gè)具有相同值類型(例如,整數(shù)3)的裝箱實(shí)例始終看起來不相等,并且不執(zhí)行任何操作。不要將此重載用于值類型。
初次看,稍不注意的迷惑性來了,解釋如下截圖
從描述上看,一部分童鞋是不是會認(rèn)為,原始值為空,則會引發(fā)空引用異常,如下代碼
- object o1 = null;
- object o2 = null;
- Interlocked.CompareExchange(ref o1, o2, null);
運(yùn)行上述代碼將不會拋出空引用異常,這是為何?難道官方解釋有誤,接下來我們深入探討下
深談如標(biāo)題
若對C語言有所了解,則不會存在疑惑,官方解釋為空指針(不是空),而我們代碼是空引用,二者不可同日而語
C#中對于引用類型,定義現(xiàn)有的變量必將存在引用,所以在C#中不可能存在空引用,所以我們是不是可以認(rèn)為必然不會存在拋出空引用異常
官方解釋為空指針和null其實(shí)并不是同一個(gè)概念,如此一解釋,極易引起概念混淆,還不如去掉,顯得有點(diǎn)多余
再想想,也不是那么絕對,個(gè)人以為,至少在托管情況下理論上應(yīng)該不會拋出任何異常,非托管情況下可能沒有保證,或者通過IL操作底層,也會觸發(fā)上述空引用異常
綜上個(gè)人理解,官方解釋談不上迷惑性,只是好像有點(diǎn)會引起概念混淆,讓部分童鞋以為不能傳遞空,希望沒將各位繞暈圖片
那么我們在哪些場景下會用到上述原始值為空的情況呢?比如確保對象初始化,如下:
- public class Order
- {
- public Address Address
- {
- get
- {
- return CreateAddress();
- }
- }
- private Address CreateAddress() => new Address();
- }
- public class Address
- {
- }
當(dāng)我們在Order內(nèi)部使用Address時(shí),確保其實(shí)例已完全初始化,如下:
- static Address EnsureAddressInitialized()
- {
- var order = new Order();
- Func<Order, Address> func = f => f.Address;
- Address target = null;
- var result = Interlocked.CompareExchange(ref target, func(order), null);
- Console.WriteLine(result == null);
- Console.WriteLine(target);
- return target!;
- }
上述方法返回對象實(shí)例時(shí),我們使用C# 8.0語法表明對象實(shí)例絕不可能為空
該語法有返回值,那打印結(jié)果是否和替換后的原始值一樣呢?不是,除了替換原始值外,針對所有情況,返回值都是最初原始值即舊值。
據(jù)我所知,該語法底層直接操作CPU處理器指令,當(dāng)然也是原子性操作,即便是操作系統(tǒng)也無法執(zhí)行中斷操作,線程可以在指令執(zhí)行之前被搶占,但在指令執(zhí)行期間不會被搶占,換言之,絕不會出現(xiàn),當(dāng)原始值和比較值比較相等時(shí),而另一線程指令更改原始值的情況
基于上述理論,所以才有了網(wǎng)上大多數(shù)通過循環(huán)方式對其返回結(jié)果賦最新原始值實(shí)現(xiàn)并發(fā)無鎖修改操作
- public static Address EnsureAddressInitialized(
- ref Address target,
- Order order, Func<Order, Address> func)
- {
- if (target != null)
- {
- return target;
- }
- while (Interlocked.CompareExchange(ref target, func(order), null) == null)
- {
- break;
- }
- return target;
- }
為避免上述沒必要的循環(huán)操作,同時(shí)也為避開CPU緩存,我們進(jìn)一步進(jìn)行代碼優(yōu)化,通過使用Volatile關(guān)鍵字獲取內(nèi)存最新存儲數(shù)據(jù),最終演變成如下這般
- public static Address EnsureAddressInitialized(
- ref Address target,
- Order order, Func<Order, Address> func)
- {
- var tmp = Volatile.Read(ref target);
- if (tmp != null)
- {
- return tmp;
- }
- Interlocked.CompareExchange(ref target, func(order), null);
- return target!;
- }
概念的混淆可能會存在使用上的疑惑,同時(shí)我們基于理論逐步優(yōu)化,實(shí)現(xiàn)并發(fā)無鎖修改操作