.NET中的執行緒本地儲存(TLS)與AsyncLocal(一)
阿新 • • 發佈:2021-01-02
一、TLS#
執行緒本地儲存(Thread Local Storage),字面意思就是專屬某個執行緒的儲存空間。變數大體上分為全域性變數和區域性變數,一個程序中的所有執行緒共享地址空間,這個地址空間被劃分為幾個固有的區域,比如堆疊區,全域性變數區等,全域性變數儲存在全域性變數區,虛擬地址固定;區域性變數儲存在堆疊區,虛擬地址不固定。每個執行緒都有自己的棧空間,區域性變數就儲存在棧空間裡面,雖然這個區域性變數是與執行緒相聯絡的,但是這個區域性變數不能在不同的函式棧中互相直接訪問,但TLS可以,概括來講,TLS是屬於執行緒的“區域性變數”,作用域為執行緒作用域,而不像全域性變數為全域性作用域,區域性變數為區域性作用域,因為這個變數獨屬於這個執行緒,所以這個變數是執行緒安全的。
二、.NET中相關的類——ThreadLocal#
程式碼更直觀,請看下面的程式碼:
1 static void Main(string[] args)
2 {
3 ThreadLocal<int> threadLocal = new ThreadLocal<int>();
4 //在主執行緒這個變數值為1
5 threadLocal.Value = 1;
6 new Thread(() => Console.WriteLine($"託管執行緒ID:{Thread.CurrentThread.ManagedThreadId} 值為:{threadLocal.Value++}" )).Start();
7 new Thread(() => Console.WriteLine($"託管執行緒ID:{Thread.CurrentThread.ManagedThreadId} 值為:{threadLocal.Value++}")).Start();
8 new Thread(() => Console.WriteLine($"託管執行緒ID:{Thread.CurrentThread.ManagedThreadId} 值為:{threadLocal.Value++}")).Start();
9 Console. WriteLine($"主執行緒ID:{Thread.CurrentThread.ManagedThreadId} 值為:{threadLocal.Value}");
10 }
輸出結果如下:
可以看見每個這個變數的值對於每個執行緒來說都是獨立的,一個執行緒對這個變數的修改只會影響本執行緒的讀取,每個執行緒都有一份拷貝。
有什麼用呢?或者使用場景是什麼呢?我覺得就是一句話——當每個執行緒都需要一個唯一的變數的時候。
比如早期版本的ASP.NET,每個執行緒處理一個Http請求,在處理這個Http請求的執行緒中,這個HttpContext在這個執行緒中是唯一的,所以在每個函式中都可以呼叫HttpContext.Current獲得當前Http請求上下文物件,為了加深理解,請看下面的程式碼
1 public class ConsoleContext
2
3 {
4
5 private static ThreadLocal<ConsoleContext> _tlsCCT = new ThreadLocal<ConsoleContext>();
6
7 private string _consoleName;
8
9 public string ConsoleName { get => _consoleName; }
10
11 public static ConsoleContext Current { get => _tlsCCT.Value; }
12
13 public ConsoleContext(string consoleName)
14
15 {
16
17 _consoleName = consoleName;
18
19 _tlsCCT.Value = this;
20
21 }
22
23 public static void ResetContext() => _tlsCCT.Value = null;
24
25 }
26
27 public static void Excute()
28
29 {
30
31 Thread.Sleep(1000 * new Random(DateTime.Now.Millisecond).Next(5,10));
32
33 Console.WriteLine("進入PrintName()");
34
35 PrintName();
36
37 }
38
39 public static void PrintName()
40
41 {
42
43 var name = ConsoleContext.Current.ConsoleName;
44
45 Console.WriteLine($"當前託管執行緒ID:{Thread.CurrentThread.ManagedThreadId} name:{name}");
46
47 }
48
49 static void Main(string[] args)
50
51 {
52
53 while (true)
54
55 {
56
57 var name = Console.ReadLine();
58
59 ThreadPool.QueueUserWorkItem(state =>
60
61 {
62
63 Console.WriteLine($"當前託管執行緒ID:{Thread.CurrentThread.ManagedThreadId} name:{name}");
64
65 new ConsoleContext(name);
66
67 Excute();
68
69 ConsoleContext.ResetContext();
70
71 });
72
73 }
74
75 }
簡單來說,我模擬了一個Web伺服器的行為,監聽[vb.net教程](https://www.xin3721.com/eschool/VBNetxin3721/)
請求(在這裡是監聽鍵盤輸入),若沒有請求過來,伺服器程式阻塞,若有請求過來(在這裡是鍵盤輸入),伺服器響應請求,生成當前請求上下文,並生成一個TLS變數,然後執行Excute函式(相當HttpContext流入處理c#教程管道),最後清空TLS變數中的值,因為該執行緒是執行緒池中的執行緒,會被複用用於處理其他請求,不清空TLS會生成髒資料。
作者: 白煙染黑墨
出處:https://www.cnblogs.com/hkfyf/p/13209283.html