成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

開發(fā) 前端
ConcurrentDictionary<TKey,TValue>絕大部分api都是線程安全的[1],唯二的例外是接收工廠函數(shù)的api:?AddOrUpdate、GetOrAdd,這兩個(gè)api不是線程安全的,需要引起重視。

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

ConcurrentDictionary<TKey,TValue>絕大部分api都是線程安全的[1],唯二的例外是接收工廠函數(shù)的api:?AddOrUpdate、GetOrAdd,這兩個(gè)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.

之前有個(gè)同事就因?yàn)檫@個(gè)case背了一個(gè)P。

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

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

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

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

A: 還不是因?yàn)槲④洸幌嘈拍隳軐懗鼋训臉I(yè)務(wù)代碼,未知的業(yè)務(wù)代碼可能造成死鎖。

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工廠函數(shù)可能會(huì)多次執(zhí)行
  • 雖然會(huì)多次執(zhí)行, 但插入的值固定是一個(gè),插入的值取決于哪個(gè)線程率先插入字典。

Q3: 怎么做到的隨機(jī)穩(wěn)定輸出一列值?A:源代碼做了double check[2]了,后續(xù)線程通過工廠類創(chuàng)建值后,會(huì)再次檢查字典,發(fā)現(xiàn)已有值,會(huì)丟棄自己創(chuàng)建的值。

示例代碼:

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個(gè)線程并發(fā)插入字典,每次隨機(jī)輸出,_runCount=4顯示工廠類執(zhí)行4次。

圖片

Q4:如果工廠產(chǎn)值的代價(jià)很大,不允許多次創(chuàng)建,如何實(shí)現(xiàn)?

筆者的同事之前就遇到這樣的問題,高并發(fā)請(qǐng)求頻繁創(chuàng)建redis連接,直接打掛了機(jī)器。

A: 有一個(gè)trick能解決這個(gè)問題: valueFactory工廠函數(shù)返回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);
}
}

圖片

上面示例,依舊會(huì)隨機(jī)穩(wěn)定輸出,但是_runOut=1表明產(chǎn)值動(dòng)作只執(zhí)行了一次、

valueFactory工廠函數(shù)返回Lazy容器是一個(gè)精妙的trick。

① 工廠函數(shù)依舊沒進(jìn)入鎖定過程,會(huì)多次執(zhí)行;

② 與最上面的例子類似,只會(huì)插入一個(gè)Lazy容器(后續(xù)線程依舊做double check發(fā)現(xiàn)字典key已經(jīng)有Lazy容器了,會(huì)放棄插入);

③ 線程執(zhí)行Lazy.Value, 這時(shí)才會(huì)執(zhí)行創(chuàng)建value的工廠函數(shù);

④ 多個(gè)線程嘗試執(zhí)行Lazy.Value, 但這個(gè)延遲初始化方式被默認(rèn)設(shè)置為ExecutionAndPublication:不僅以線程安全的方式執(zhí)行, 而且確保只會(huì)執(zhí)行一次構(gòu)造函數(shù)。

public Lazy(Func<T> valueFactory)
:this(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor: false)
{
}

控制構(gòu)造函數(shù)執(zhí)行的枚舉值

描述

ExecutionAndPublication[3]

能確保只有一個(gè)線程能夠以線程安全方式執(zhí)行構(gòu)造函數(shù)

None

線程不安全

Publication

并發(fā)線程都會(huì)執(zhí)行初始化函數(shù),以先完成初始化的值為準(zhǔn)

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

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

總結(jié)

為解決ConcurrentDictionary GetOrAdd(key, valueFactory) 工廠函數(shù)在并發(fā)場(chǎng)景下被多次執(zhí)行的問題:

① valueFactory工廠函數(shù)產(chǎn)生Lazy容器;

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

兩姿勢(shì)缺一不可。

責(zé)任編輯:武曉燕 來源: 精益碼農(nóng)
相關(guān)推薦

2018-07-04 06:18:07

2019-06-28 13:15:58

機(jī)器人勞動(dòng)力人工成本

2024-04-03 08:25:11

DictionaryC#字典類型

2009-03-10 09:58:00

TrapezeWi-FiWLAN

2009-10-13 14:49:00

工作求職之路

2024-09-17 17:50:28

線程線程安全代碼

2022-07-08 16:10:55

線程安全對(duì)象

2011-03-23 10:00:54

2021-12-06 10:10:11

云原生安全公共云私有云

2021-07-26 06:57:59

Synchronize線程安全

2018-08-17 09:00:00

2017-10-26 14:01:19

華為工作程序員

2020-06-24 07:43:59

公共安全IOT

2014-04-29 09:15:00

2018-12-24 09:14:59

2021-12-21 21:16:30

LinuxLinux大事

2016-12-02 08:53:18

Python一行代碼

2011-05-03 17:12:17

2009-12-14 18:30:16

水星無線路由器參數(shù)評(píng)測(cè)

2019-01-02 06:26:02

API應(yīng)用程序編程接口應(yīng)用安全
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 午夜精品久久久久久不卡欧美一级 | 国产精品一区二区久久 | 涩色视频在线观看 | 成年人在线| 99久久婷婷国产综合精品电影 | 色影视| 日本精品在线一区 | 亚洲一区二区三区在线播放 | 99热在线免费 | 在线免费观看日本 | 91久久爽久久爽爽久久片 | 久久99精品久久久久 | 国产在线视频三区 | 午夜精品久久久久久久久久久久 | 久久精品国产一区 | 久久久久久91 | 日韩免费视频一区二区 | 亚洲国产一区二区三区 | 中文字幕一区二区三区四区五区 | 中文字幕在线一 | 亚洲精品一区二区冲田杏梨 | 国产成人午夜精品影院游乐网 | 亚洲精品视频在线 | 韩国av网站在线观看 | 黄色欧美视频 | 国产一区影院 | 国产免费一区二区三区 | 美女天天操 | 91大片| 99re在线视频免费观看 | 99这里只有精品视频 | 日日日操 | 一级毛片免费完整视频 | 91麻豆精品国产91久久久久久久久 | 欧美午夜影院 | 99久久免费精品视频 | www.天天操.com | 丁香一区二区 | 99精品欧美一区二区蜜桃免费 | 国产日产欧产精品精品推荐蛮挑 | 天天色综网 |