C#多線程之線程綁定ThreadLocal類
在.Net 4.0的Thread里,新增了線程局部變量(ThreadLocal)類,可以很方便的實(shí)現(xiàn)線程專有存儲(chǔ)。
應(yīng)用場(chǎng)景
線程專有存儲(chǔ)應(yīng)被用于這樣的多線程應(yīng)用:它們經(jīng)常訪問(wèn)那些邏輯上是全局的、而物理上是專有于每個(gè)線程的對(duì)象。首先我們看如下這樣一個(gè)例子
string errorMessage;
void Process()
{
bool ret = Run();
if (!ret && needDebug)
{
Console.WriteLine(errorMessage);
}
}
bool Run()
{
try
{
//…-- do something
return true;
}
catch (Exception e)
{
errorMessage = e.Message;
return false;
}
}這個(gè)函數(shù)中,Process為主體函數(shù),當(dāng)它調(diào)用Run函數(shù)失敗后,為調(diào)式方便,打出Run函數(shù)的錯(cuò)誤信息。錯(cuò)誤信息采用成員變量errorMessage存放,為了減少Run函數(shù)的參數(shù)。
這種通過(guò)成員變量errorMessage在函數(shù)間傳遞信息的方式在單線程程序中可以很好的工作,但是在多線程應(yīng)用時(shí)卻往往會(huì)發(fā)生一些微妙的問(wèn)題:當(dāng)兩個(gè)線程同時(shí)執(zhí)行Run函數(shù)時(shí),先執(zhí)行的會(huì)被后執(zhí)行的線程覆蓋,導(dǎo)致輸出了錯(cuò)誤的后執(zhí)行的線程的調(diào)試信息。發(fā)生類似數(shù)據(jù)庫(kù)的臟讀錯(cuò)誤。
解決方案:
最直接的解決方案有兩種:
加鎖:在Process中加鎖,保證沒(méi)有兩個(gè)線程同時(shí)訪問(wèn)errorMessage
修改Run函數(shù)為bool Run(out string errorMessage)的形式,不通過(guò)errorMessage共享數(shù)據(jù),使其支持并發(fā)操作。
這兩種方式都是有效的,但都有一些不足:加鎖時(shí)獲取和釋放互斥體有一個(gè)不小的開銷,當(dāng)共享的數(shù)據(jù)較多時(shí)修改Run函數(shù)會(huì)導(dǎo)致Run函數(shù)變得很難看,并且可能會(huì)由于改動(dòng)較大而導(dǎo)致大規(guī)模重構(gòu)。
針對(duì)上述兩種方式的不足,人們提出了線程專有存儲(chǔ)的解決方案,使用ThreadLocal類的解決方案如下:
ThreadLocal<string> errorMessage = new ThreadLocal<string> ();
void Process()
{
bool ret = Run();
if (!ret && needDebug)
{
Console.WriteLine(errorMessage);
}
}
bool Run()
{
try
{
…- do something
return true;
}
catch (Exception e)
{
errorMessage.Value=e.Message;
return false;
}
}ThreadLocal類在每個(gè)線程下都分配一個(gè)獨(dú)立實(shí)例副本,每個(gè)線程都只訪問(wèn)到自己的實(shí)例,不會(huì)影響其它線程,從而解決讀臟數(shù)據(jù)的問(wèn)題。
ThreadLocal類也不是什么新概念,在C++、Java等語(yǔ)言的線程庫(kù)中都有相關(guān)實(shí)現(xiàn),一些語(yǔ)言編譯器實(shí)現(xiàn)(如IBM XL FORTRAN)中甚至在語(yǔ)言的層次提供了直接的支持。其實(shí)實(shí)現(xiàn)的思路很簡(jiǎn)單:在ThreadLocal類中有一個(gè)哈希表,根據(jù)線程ID為key用于存儲(chǔ)每一個(gè)線程的變量的副本。由于現(xiàn)在沒(méi)啥相關(guān)資料,并且也是beta版的,我也懶得對(duì).Net中的具體實(shí)現(xiàn)和性能進(jìn)一步分析。
和上面的兩種方式相比,線程專有存儲(chǔ)有如下好處:
- 效率:線程專有存儲(chǔ)可實(shí)現(xiàn)成無(wú)需對(duì)線程專有數(shù)據(jù)進(jìn)行鎖定。例如,通過(guò)將errno放入線程專有存儲(chǔ)中,每個(gè)線程都可以可靠地設(shè)置和測(cè)試該線程中的方法的完成狀態(tài),而無(wú)需使用復(fù)雜的同步協(xié)議。這排除了線程中共享數(shù)據(jù)的鎖定開銷,比起獲取和釋放互斥體要更為迅捷。
- 易于使用:對(duì)于應(yīng)用程序員來(lái)說(shuō),線程專有存儲(chǔ)使用起來(lái)很簡(jiǎn)單,因?yàn)橄到y(tǒng)開發(fā)者可以通過(guò)數(shù)據(jù)抽象或宏來(lái)使線程專有存儲(chǔ)的使用在源碼級(jí)完全透明化。
但也存在如下缺點(diǎn):
- 它鼓勵(lì)了(線程安全的)全局變量的使用:許多應(yīng)用不要求多個(gè)線程通過(guò)公用訪問(wèn)點(diǎn)來(lái)訪問(wèn)線程專有的數(shù)據(jù)。如果是這樣,數(shù)據(jù)的存儲(chǔ)應(yīng)使只有擁有該數(shù)據(jù)的線程可對(duì)它進(jìn)行訪問(wèn)。
- 它隱藏了系統(tǒng)的結(jié)構(gòu):線程專有存儲(chǔ)的使用隱藏了應(yīng)用中的對(duì)象之間的關(guān)系,可能會(huì)導(dǎo)致應(yīng)用更難被理解。
適用性
應(yīng)用有以下特性時(shí)可使用線程專有存儲(chǔ):
- 應(yīng)用最初的編寫假定了單線程控制,并正在被移植到多線程環(huán)境,而又不能改變現(xiàn)有API
- 應(yīng)用含有多個(gè)占先式線程控制,可以任意的調(diào)度順序并發(fā)執(zhí)行;
- 每個(gè)線程控制調(diào)用一系列方法,這些方法共享只對(duì)該線程來(lái)說(shuō)是公用的數(shù)據(jù);
- 在每個(gè)線程中被對(duì)象共享的數(shù)據(jù)必須通過(guò)一個(gè)全局可見的訪問(wèn)點(diǎn)來(lái)訪問(wèn);
- 訪問(wèn)點(diǎn)"邏輯地"與其他線程共享,但在"物理上" 對(duì)于每個(gè)線程卻是唯一的;
- 數(shù)據(jù)在方法間隱式地傳遞,而不是經(jīng)由參數(shù)顯式地傳遞。
理解上面描述的特性對(duì)于使用(或不使用)線程專有存儲(chǔ)模式來(lái)說(shuō)是至關(guān)緊要的。例如,UNIX errno變量是一個(gè)數(shù)據(jù)例子:(1)邏輯上全局,但是物理上線程專有,以及(2)在方法間隱式地傳遞。
當(dāng)應(yīng)用有以下特性時(shí),不要使用線程專有存儲(chǔ)模式:
- 多個(gè)線程為單個(gè)任務(wù)協(xié)同工作,該任務(wù)需要并發(fā)訪問(wèn)共享數(shù)據(jù)。
例如,多線程應(yīng)用可以對(duì)在內(nèi)存中的數(shù)據(jù)庫(kù)并發(fā)地進(jìn)行讀寫。在這樣的情況下,線程必須共享不是線程專有的記錄和表。如果使用線程專有存儲(chǔ)來(lái)存儲(chǔ)此數(shù)據(jù)庫(kù),線程就不能共享這些數(shù)據(jù)。因而,對(duì)數(shù)據(jù)庫(kù)記錄的訪問(wèn)必須通過(guò)同步原語(yǔ)(例如,互斥體)來(lái)控制,以使線程能在共享數(shù)據(jù)上協(xié)作。 - 維護(hù)物理和邏輯上都分離的數(shù)據(jù)要更為直觀和高效。
例如,通過(guò)將數(shù)據(jù)作為參數(shù)顯式地傳遞給所有方法,有可能使線程訪問(wèn)僅在每個(gè)線程中可見的數(shù)據(jù)。在這樣的情況下,線程專有存儲(chǔ)模式有可能是不必要的。
到此這篇關(guān)于C#線程綁定ThreadLocal類的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#實(shí)現(xiàn)將32位MD5摘要串轉(zhuǎn)換為128位二進(jìn)制字符串的方法
這篇文章主要介紹了C#實(shí)現(xiàn)將32位MD5摘要串轉(zhuǎn)換為128位二進(jìn)制字符串的方法,涉及C#字符串遍歷、加密與轉(zhuǎn)換相關(guān)操作技巧,需要的朋友可以參考下2017-04-04
利用Aspose.Cells和Excel模板導(dǎo)出統(tǒng)計(jì)數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了利用Aspose.Cells和Excel模板導(dǎo)出復(fù)雜的統(tǒng)計(jì)數(shù)據(jù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
基于WPF實(shí)現(xiàn)簡(jiǎn)單的值轉(zhuǎn)換器
值轉(zhuǎn)換器是?WPF?項(xiàng)目中具有特色的組成部分,這篇文章將帶大家實(shí)現(xiàn)一個(gè)標(biāo)準(zhǔn)的值轉(zhuǎn)換器,文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考一下2025-02-02
C#編程中常見數(shù)據(jù)結(jié)構(gòu)的比較(Unity3D游戲開發(fā))
在本篇內(nèi)容里我們給大家整理了關(guān)于Unity3D游戲開發(fā)中C#編程中常見數(shù)據(jù)結(jié)構(gòu)的比較相關(guān)知識(shí)點(diǎn)內(nèi)容,需要的朋友們參考下。2019-05-05
C#調(diào)用RabbitMQ實(shí)現(xiàn)消息隊(duì)列的示例代碼
這篇文章主要介紹了C#調(diào)用RabbitMQ實(shí)現(xiàn)消息隊(duì)列的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12

