ConcurrentDictionary字典操作竟然不全是線程安全的?

53f8f64f2009355d5ecbab497277dae8.gif

好久不見,馬甲哥封閉居家半個月,記錄之前遇到的一件小事。

ConcurrentDictionary<TKey,TValue>絕大部分api都是線程安全的[1]
唯二的例外是接收工廠函數的api:AddOrUpdateGetOrAdd,這兩個api不是線程安全的,需要引起重視。

All these operations are atomic and are thread-safe with regards to all other operations on the ConcurrentDictionary<TKey,TValue> class. The only exceptions are the methods that accept a delegate, that is, AddOrUpdate and GetOrAdd.

之前有個同事就因為這個case背了一個P。

AddOrUpdate(TKey, TValue, Func<TKey,TValue,TValue> valueFactory);

GetOrAdd(TKey key, Func<TKey, TValue> valueFactory);
(注意,包括其他接收工廠委托的重載函數)

整個過程中涉及與字典直接交互的都用到到精細鎖,valueFactory工廠函數在鎖定區外面被執行,因此,這些代碼不受原子性約束。

Q1: valueFactory工廠函數不在鎖定范圍,為什么不在鎖范圍?

A: 還不是因為微軟不相信你能寫出健壯的業務代碼,未知的業務代碼可能造成死鎖。

However, delegates for these methods are called outside the locks to avoid the problems that can arise from executing unknown code under a lock. Therefore, the code executed by these delegates is not subject to the atomicity of the operation.

Q2:帶來的效果?

  • ??valueFactory工廠函數可能會多次執行

  • ??雖然會多次執行, 但插入的值固定是一個,插入的值取決于哪個線程率先插入字典。

    Q3: 怎么做到的隨機穩定輸出一列值?
    A:源代碼做了double check[2]了,后續線程通過工廠類創建值后,會再次檢查字典,發現已有值,會丟棄自己創建的值。

示例代碼:

using?System.Collections.Concurrent;public?class?Program
{private?static?int?_runCount?=?0;private?static?readonly?ConcurrentDictionary<string,?string>?_dictionary=?new?ConcurrentDictionary<string,?string>();public?static?void?Main(string[]?args){var?task1?=?Task.Run(()?=>?PrintValue("The?first?value"));var?task2?=?Task.Run(()?=>?PrintValue("The?second?value"));var?task3?=?Task.Run(()?=>?PrintValue("The?three?value"));var?task4?=?Task.Run(()?=>?PrintValue("The?four?value"));Task.WaitAll(task1,?task2,?task4,task4);PrintValue("The?five?value");Console.WriteLine($"Run?count:?{_runCount}");}public?static?void?PrintValue(string?valueToPrint){var?valueFound?=?_dictionary.GetOrAdd("key",x?=>{Interlocked.Increment(ref?_runCount);Thread.Sleep(100);return?valueToPrint;});Console.WriteLine(valueFound);}
}

上面4個線程并發插入字典,每次隨機輸出,_runCount=4顯示工廠類執行4次。

e9c151a2b82e42bbb2fdb1b6f43e66d7.png

Q4:如果工廠產值的代價很大,不允許多次創建,如何實現?

筆者的同事之前就遇到這樣的問題,高并發請求頻繁創建redis連接,直接打掛了機器。

A: 有一個trick能解決這個問題:?valueFactory工廠函數返回Lazy容器.

using?System.Collections.Concurrent;public?class?Program
{private?static?int?_runCount2?=?0;private?static?readonly?ConcurrentDictionary<string,?Lazy<string>>?_lazyDictionary=?new?ConcurrentDictionary<string,?Lazy<string>>();public?static?void?Main(string[]?args){task1?=?Task.Run(()?=>?PrintValueLazy("The?first?value"));task2?=?Task.Run(()?=>?PrintValueLazy("The?second?value"));task3?=?Task.Run(()?=>?PrintValueLazy("The?three?value"));task4?=?Task.Run(()?=>?PrintValueLazy("The?four?value"));????Task.WaitAll(task1,?task2,?task4,?task4);PrintValue("The?five?value");Console.WriteLine($"Run?count:?{_runCount2}");}public?static?void?PrintValueLazy(string?valueToPrint){var?valueFound?=?_lazyDictionary.GetOrAdd("key",x?=>?new?Lazy<string>(()?=>{Interlocked.Increment(ref?_runCount2);Thread.Sleep(100);return?valueToPrint;}));Console.WriteLine(valueFound.Value);}
}
12638e95923123709dec40534fbbeb4a.png

上面示例,依舊會隨機穩定輸出,但是_runOut=1表明產值動作只執行了一次、

valueFactory工廠函數返回Lazy容器是一個精妙的trick。

① 工廠函數依舊沒進入鎖定過程,會多次執行;

② 與最上面的例子類似,只會插入一個Lazy容器(后續線程依舊做double check發現字典key已經有Lazy容器了,會放棄插入);

③ 線程執行Lazy.Value, 這時才會執行創建value的工廠函數;

④ 多個線程嘗試執行Lazy.Value, 但這個延遲初始化方式被默認設置為ExecutionAndPublication:
不僅以線程安全的方式執行, 而且確保只會執行一次構造函數。

public?Lazy(Func<T>?valueFactory):this(valueFactory,?LazyThreadSafetyMode.ExecutionAndPublication,?useDefaultConstructor:?false)
{
}
控制構造函數執行的枚舉值描述
ExecutionAndPublication[3]能確保只有一個線程能夠以線程安全方式執行構造函數
None線程不安全
Publication并發線程都會執行初始化函數,以先完成初始化的值為準

IHttpClientFactory在構建<命名HttpClient,活躍連接Handler>字典時, 也用到了這個技巧,大家自行欣賞DefaultHttpCLientFactory源碼[4]

  • ??https://andrewlock.net/making-getoradd-on-concurrentdictionary-thread-safe-using-lazy/


總結

為解決ConcurrentDictionary GetOrAdd(key, valueFactory) 工廠函數在并發場景下被多次執行的問題:

① valueFactory工廠函數產生Lazy容器;

② 將Lazy容器的值初始化姿勢設定為ExecutionAndPublication(線程安全且執行一次)。

兩姿勢缺一不可。

本人會不時修正理解、更正錯誤,請適時移步左下角永久更新地址;也請看客大膽斧正。

引用鏈接

[1]?ConcurrentDictionary<TKey,TValue>絕大部分api都是線程安全的:?https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2?view=net-6.0
[2]?double check:?https://github.com/dotnet/runtime/blob/main/src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs#L1152
[3]?ExecutionAndPublication:?https://docs.microsoft.com/en-us/dotnet/api/system.threading.lazythreadsafetymode?view=net-6.0#system-threading-lazythreadsafetymode-executionandpublication
[4]?DefaultHttpCLientFactory源碼:?https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs#L118

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/282746.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/282746.shtml
英文地址,請注明出處:http://en.pswp.cn/news/282746.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

碼農小汪-Hibernate學習8-hibernate關聯關系注解表示@OneToMany mappedBy @ManyToMany @JoinTable...

近期我也是有點郁悶&#xff0c;究竟是程序中處理關聯關系。還是直接使用外鍵處理關聯關系呢&#xff1f;這個的說法不一致&#xff01;程序中處理這樣的關聯關系的話。自己去維護這樣的約束。這樣的非常樂觀的一種做法&#xff01;或者是直接在數據庫中處理這樣的直接的外鍵關…

HTML中彈窗中加入圖片,javascript里怎么實現點擊圖片彈出對話框?

JavaScript中可以使用document.getElementsByTagName方法后去img標簽&#xff0c;然后遍歷所有img標簽并為其添加點擊事件實現點擊彈出對話框。JavaScript實現點擊圖片彈出對話框&#xff1a;img {width: 500px;height: 300px;}//獲取所有的img標簽var imgObjs document.getEl…

Java學習優秀網站

各類程序員學習路線圖&#xff1a; http://www.runoob.com/coder-learn-path 博學谷&#xff1a; http://v.itcast.cn/map/22.html 慕課網&#xff1a; http://www.imooc.com/course/programdetail/pid/31 轉載于:https://www.cnblogs.com/Arsene/p/6441831.html

Dcloud課程2 什么是Dcloud

Dcloud課程2 什么是Dcloud 一、總結 一句話總結&#xff1a;DCloud提供了一套快速開發應用的跨平臺技術方案。 1、DCloud的產品架構&#xff1f; MUI(H5)HBuilder 2、什么是MUI&#xff1f; 最接近原生體驗的移動App的UI框架。 3、什么是H5&#xff1f; html5功能增強標準 二、…

html5 輪詢自動刷新數據,后臺調用exe,前端定時輪詢調用結果

前提使用asp.net core 2.1前端使用vueui使用element-ui前端發送請求用Axios新建asp.net core程序1.jpg修改Index.html{Layout null;}test{{ msg }}發送請求打開記事本// 創建 Vue 實例&#xff0c;得到 ViewModelvar vm new Vue({el: #app,data: {msg: 準備發送請求打開exe},…

洛谷 P2951 [USACO09OPEN]捉迷藏Hide and Seek

題目描述 Bessie is playing hide and seek (a game in which a number of players hide and a single player (the seeker) attempts to find them after which various penalties and rewards are assessed; much fun usually ensues). She is trying to figure out in which…

使用ssh免密碼登錄Linux服務器

頻繁登錄Linux服務器時&#xff0c;使用ssh <username><host>的方式登錄&#xff0c;但是每次都需要輸入密碼是件很麻煩的事。我們還可以使用私鑰/公鑰對的方式在免密碼登錄服務器。首先需要在遠程服務器中安裝ssh-server服務&#xff0c;才可以使用ssh登錄。如果沒…

linux下tomcat開啟遠程調試

1.center下&#xff0c;在startup.sh文件首行中添加如下語句 declare -x CATALINA_OPTS"-server -Xdebug -Xnoagent -Djava.compilerNONE -Xrunjdwp:transportdt_socket,servery,suspendn,address8000"(不要換行&#xff0c;要在同一行)Ubuntu下&#xff0c;在catali…

.NET 7 RC1 發布

原文鏈接&#xff1a;https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-rc-1/[1]原文作者&#xff1a;Jeremy Likness&#xff0c;Angelos Petropoulos&#xff0c;Jon Douglas翻譯&#xff1a;沙漠盡頭的狼(谷歌翻譯加持)今天我們宣布 .NET 7 候選版本 1。這是生產…

html 字符串最后加空格,html space空格符

htmlcss 代碼在網頁中如何插入打出空格字符實現方法**摘要瀏覽器總是會截短 HTML 頁面中的空格。HTML將所有空格字符&#xff0c;制表符&#xff0c;空格和回車符壓縮為一個字符。如果要縮進段落&#xff0c;則不能簡單地鍵入五個空格然后開始文本。 如果您在文本中寫 10 個空格…

.NET MAUI實戰 FilePicker

1.概要最近在遷移 GeneralUpdate.Tool的時候需要用到文件選擇&#xff0c;在MAUI中可以使用FilePicker進行選擇。ref1: https://gitee.com/Juster-zhu/GeneralUpdateref2:https://docs.microsoft.com/zh-cn/dotnet/maui/platform-integration/storage/file-picker?tabswindows…

SQL Server中,with as使用介紹

一&#xff0e;WITH AS的含義 WITH AS短語&#xff0c;也叫做子查詢部分&#xff08;subquery factoring&#xff09;&#xff0c;可以讓你做很多事情&#xff0c;定義一個SQL片斷&#xff0c;該SQL片斷會被整個SQL語句所用到。有的時候&#xff0c;是為了讓SQL語句的可讀…

從新手機到老股票 閑魚為何會淪為騙子與營銷的新平臺?

國內電商一直空缺一個有規模的綜合二手交易平臺。閑魚的出現&#xff0c;有一定程度上滿足了喜歡淘二手、喜歡“撿漏”的用戶需求。雖加入了擔保和第三方支付等環節&#xff0c;但這種隨機的二手交易行為不可避免地會出現上當、受騙的情況出現。本質上來說&#xff0c;閑魚仍然…

網上書店模板asp與html,一個簡單的網上書城的例子(三)_asp實例

buy.asp:顯示商品和用戶購物&#xff01;DbPath SERVER.MapPath("ShopBag.mdb")Set conn Server.CreateObject("ADODB.Connection")conn.open "driver{Microsoft Access Driver (*.mdb)};dbq" & DbPathCategoryIDRequest("CategoryID…

使用C#編寫一個.NET分析器(一)

譯者注這是在Datadog公司任職的Kevin Gosse大佬使用C#編寫.NET分析器的系列文章之一&#xff0c;在國內只有很少很少的人了解和研究.NET分析器&#xff0c;它常被用于APM&#xff08;應用性能診斷&#xff09;、IDE、診斷工具中&#xff0c;比如Datadog的APM&#xff0c;Visual…

內置數據類型

Java語言提供了八種基本類型。六種數字類型&#xff08;四個整數型&#xff0c;兩個浮點型&#xff09;&#xff0c;一種字符類型&#xff0c;還有一種布爾型。 byte&#xff1a; byte 數據類型是8位、有符號的&#xff0c;以二進制補碼表示的整數&#xff1b; 最小值是 -128&…

算法學習之循環結構程序設計

for循環 打印1,2,3&#xff0c;...&#xff0c;n每個占一行。 #include <conio.h> #include<stdio.h> int main(){int i,n;scanf("%d",&n);for(i1;i<n;i){printf("%d\n",i);}getch();return 0; } 分支結合循環&#xff0c;威力很強大 輸…

Linux常用命令 (分門別類)

一、系統安全: su: 用于切換當前用戶身份到其他用戶身份&#xff0c;變更時須輸入所要變更的用戶帳號與密碼 sudo: 用來以其他身份來執行命令&#xff0c;預設的身份為root lastlog: 用于顯示系統中所有用戶最近一次登錄信息 lastb: 用于顯示用戶錯誤的登錄列表&#x…

hibernate自定義校驗器使用(字段在in范圍之內)

2019獨角獸企業重金招聘Python工程師標準>>> 1.自定義注解類DigitsMustIn Constraint(validatedBy DigitsMustInValidator.class) //具體的實現 Target({java.lang.annotation.ElementType.METHOD,java.lang.annotation.ElementType.FIELD}) Retention(java.lang.a…

sql將html轉成excel,使用SQL*PLUS,構建完美excel或html輸出

通過SQL*PLUS我們可以構建友好的輸出&#xff0c;滿足多樣化用戶需求。本例通過簡單示例&#xff0c;介紹通過sql*plus輸出xls&#xff0c;html兩種格式文件.首先創建兩個腳本:1.main.sql用以設置環境&#xff0c;調用具體功能腳本2.功能腳本-get_tables.sql為實現具體功能之腳…