C# 兩大線程本地存儲解決方案:ThreadStatic 與 ThreadLocal
C# 兩大線程本地存儲解決方案:ThreadStatic 與 ThreadLocal
一、線程本地存儲
在 C# 中,static 關(guān)鍵字定義的變量,其作用域是在應(yīng)用程序域(AppDomain)內(nèi)共享的。因此,在多線程操作時,對同一個靜態(tài)變量進行操作可能會導(dǎo)致并發(fā)問題,如鎖競爭等。這種情況下,我們需要一種機制,使某些變量對每個線程獨立,從而避免鎖競爭問題。
線程本地存儲(Thread-Local Storage,TLS)是一種機制,允許每個線程擁有自己的變量副本。這意味著每個線程對變量的訪問都是獨立的,互不干擾。
例如,線程安全的 ConcurrentBag 底層的實現(xiàn)就用到了 ThreadLocal 變量,來實現(xiàn)線程間的獨立數(shù)據(jù)存儲。
二、C# 中的解決方案
1. ThreadStatic 特性
概念與用法
ThreadStatic 是一個用于靜態(tài)字段的特性,可以使字段在每個線程中都有一個獨立的實例。
示例代碼
internal class Program
{
[ThreadStatic]
private static int _threadSpecificData = -1;
static void Main(string[] args)
{
Task.Run(() =>
{
_threadSpecificData = 1;
Console.WriteLine($"第1個線程,線程id: {Environment.CurrentManagedThreadId}: {_threadSpecificData}");
});
Task.Run(() =>
{
_threadSpecificData = 2;
Console.WriteLine($"第2個線程,線程id:{Environment.CurrentManagedThreadId}: {_threadSpecificData}");
});
Console.ReadKey();
}
}
輸出:
第1個線程,線程id: 9: 1
第2個線程,線程id:8: 2
注意事項
只能用于 static 字段。
每個線程使用變量前,初始化變量。定義變量時,賦值的變量初始值,只會有一個線程的變量有這個初始值,其他線程變量副本是沒有賦值的狀態(tài)。特別主要引用類型的變量,會是null.
2. ThreadLocal 類
概念與用法
ThreadLocal<T> 是 .NET 提供的一個泛型類,用于更靈活地實現(xiàn)線程本地存儲。它允許每個線程擁有獨立的值,并支持通過工廠方法初始化每個線程的值。
示例代碼
internal class Program
{
static void Main(string[] args)
{
ThreadLocal<int> _threadSpecificData = new ThreadLocal<int>(() => Thread.CurrentThread.ManagedThreadId);
Task.Run(() =>
{
Console.WriteLine($"第1個線程,線程id: {Environment.CurrentManagedThreadId}: value: {_threadSpecificData}");
});
Task.Run(() =>
{
Console.WriteLine($"第1個線程,線程id: {Environment.CurrentManagedThreadId}: value: {_threadSpecificData}");
});
Console.ReadKey();
}
}
輸出:
第1個線程,線程id: 8: value: 8
第1個線程,線程id: 6: value: 6
特點
支持 Func 委托延遲初始化
區(qū)別
| 特性 | ThreadStatic | ThreadLocal |
|---|---|---|
| 使用范圍 | 僅用于 static 字段 | 不限制 |
| 初始化方式 | 無法直接初始化,需顯式賦值 | 支持延遲初始化 |
| 管理靈活性 | 簡單,但功能受限 | 功能強大,適合復(fù)雜場景 |
三、線程本地存儲到底存儲在什么地方
線程本地存儲的引用地址是存在 TLS(Thread Local Storage)上。
TLS(Thread Local Storage): 用于存儲線程私有數(shù)據(jù)。
TEB(Thread Environment Block): 在線程的本地存儲之上,是線程的環(huán)境塊,保存了線程的上下文信息。
PEB(Process Environment Block): 進程環(huán)境塊,用于存儲進程級的全局信息。
以下是內(nèi)存結(jié)構(gòu)的示意圖:
+----------------+
| PEB |
+----------------+
| TEB | <-- 多個線程
+----------------+
| TLS |
+----------------+
四、應(yīng)用場景
示例 1:用于日志記錄中的線程上下文
在日志記錄中,通過線程本地存儲可以為每個線程存儲獨立的上下文信息。在多線程應(yīng)用程序中,每個線程可能需要記錄不同級別的日志。使用ThreadLocal可以為每個線程分配一個獨立的日志記錄器實例,從而避免日志記錄的競爭和同步問題。
internal class Program
{
static void Main(string[] args)
{
Task.Run(() =>
{
logger.SetContext("線程A");
logger.log("在執(zhí)行第1步操作");
Thread.Sleep(200);
logger.log("在執(zhí)行第2步操作");
logger.log("執(zhí)行完了");
});
Task.Run(() =>
{
logger.SetContext("線程B");
logger.log("在執(zhí)行第1步操作");
logger.log("遇到異常,退出了");
});
Console.ReadKey();
}
}
class logger
{
private static ThreadLocal<string> _context = new ThreadLocal<string>();
public static void SetContext(string context)
{
_context.Value = context;
}
public static void log(string message)
{
Console.WriteLine($"[{_context.Value}]{message}");
}
}
輸出:
[線程A]在執(zhí)行第1步操作
[線程B]在執(zhí)行第1步操作
[線程B]遇到異常,退出了
[線程A]在執(zhí)行第2步操作
[線程A]執(zhí)行完了
五、總結(jié)
C# 提供了兩種線程本地存儲解決方案:
ThreadStatic:簡單直接,但初始化靈活性較差。
ThreadLocal:功能強大,適合更復(fù)雜的場景。
根據(jù)具體應(yīng)用場景選擇合適的方案,可以顯著提高程序的性能和線程安全性。

浙公網(wǎng)安備 33010602011771號