C#執行緒本地儲存:LocalDataStoreSlot和ThreadLocal

簡單的綠竹發表於2016-04-13


http://www.csharpwin.com/csharpspace/13101r4743.shtml


1. 使用ThreadStatic特性

ThreadStatic特性是最簡單的TLS使用,且只支援靜態欄位,只需要在欄位上標記這個特性就可以了:
 

//TLS中的str變數 [ThreadStatic] static string str = "hehe"; static void Main() { //另一個執行緒只會修改自己TLS中的hehe Thread th = new Thread(() => { str = "Mgen"; Display(); }); th.Start(); th.Join(); Display(); } static void Display() { Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, str); }
執行結果:
1 hehe 3 Mgen

可以看到,str靜態欄位在兩個執行緒中都是獨立儲存的,互相不會被修改。

2. 使用命名的LocalDataStoreSlot型別

顯然ThreadStatic特性只支援靜態欄位太受限制了。.NET執行緒型別中的LocalDataStoreSlot提供更好的TLS支援。我們先來看看命名的LocalDataStoreSlot型別,可以通過Thread.AllocateNamedDataSlot來分配一個命名的空間,通過Thread.FreeNamedDataSlot來銷燬一個命名的空間。空間資料的獲取和設定則通過Thread型別的GetData方法和SetData方法。

來看程式碼:

static void Main() { //建立Slot LocalDataStoreSlot slot = Thread.AllocateNamedDataSlot("slot"); //設定TLS中的值 Thread.SetData(slot, "hehe"); //修改TLS的執行緒 Thread th = new Thread(() => { Thread.SetData(slot, "Mgen"); Display(); }); th.Start(); th.Join(); Display(); //清除Slot Thread.FreeNamedDataSlot("slot"); } //顯示TLS中Slot值 static void Display() { LocalDataStoreSlot dataslot = Thread.GetNamedDataSlot("slot"); Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, Thread.GetData(dataslot)); }
輸出:
3 Mgen 1 hehe

3. 使用未命名的LocalDataStoreSlot型別

執行緒同樣支援未命名的LocalDataStoreSlot,未命名的LocalDataStoreSlot不需要手動清除,分配則需要Thread.AllocateDataSlot方法。注意由於未命名的LocalDataStoreSlot沒有名稱,因此無法使用Thread.GetNamedDataSlot方法,只能在多個執行緒中引用同一個LocalDataStoreSlot才可以對TLS空間進行操作,將上面的命名的LocalDataStoreSlot程式碼改成未命名的LocalDataStoreSlot執行:

//靜態LocalDataStoreSlot變數 static LocalDataStoreSlot slot; static void Main() { //建立Slot slot = Thread.AllocateDataSlot(); //設定TLS中的值 Thread.SetData(slot, "hehe"); //修改TLS的執行緒 Thread th = new Thread(() => { Thread.SetData(slot, "Mgen"); Display(); }); th.Start(); th.Join(); Display(); } //顯示TLS中Slot值 static void Display() { Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, Thread.GetData(slot)); }
輸出和上面的類似。

4. 使用.NET 4.0的ThreadLocal<T>型別

.NET 4.0線上程方面加入了很多東西,其中就包括ThreadLocal<T>型別,他的出現更大的簡化了TLS的操作。ThreadLocal<T>型別和Lazy<T>驚人相似,建構函式引數是Func<T>用來建立物件(當然也可以理解成物件的預設值),然後用Value屬性來得到或者設定這個物件。

ThreadLocal的操作或多或少有點像上面的未命名的LocalDataStoreSlot,但ThreadLocal感覺更簡潔更好理解。

程式碼:

static ThreadLocal<string> local; static void Main() { //建立ThreadLocal並提供預設值 local = new ThreadLocal<string>(() => "hehe"); //修改TLS的執行緒 Thread th = new Thread(() => { local.Value = "Mgen"; Display(); }); th.Start(); th.Join(); Display(); } //顯示TLS中資料值 static void Display() { Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, local.Value); }
輸出:
3 Mgen 1 hehe

5. 強調一下不同方法和TLS的預設值

上面程式碼都是一個一個執行緒設定值,另一個執行緒直接修改值然後輸出,不會覺察到TLS中預設值的狀況,下面專門強調一下不同方法的預設值狀況。

ThreadStatic不提供預設值:

[ThreadStatic] static int i = 123; static void Main() { //輸出本地執行緒TLS資料值 Console.WriteLine(i); //輸出另一個執行緒TLS資料值 ThreadPool.QueueUserWorkItem(_ => Console.WriteLine(i)); //控制檯等待執行緒結束 Console.ReadKey(); }
輸出:
123 0

顯然本地執行緒TLS資料時123,而靜態變數的預設值不會在另一個執行緒中初始化的。

LocalDataStoreSlot很容易可以看出來,不可能有預設值,因為初始化只能構造一個空間,而不能賦予它值,Thread.SetData顯然只會在TLS中設定資料,還是用程式碼演示一下:

static LocalDataStoreSlot slot = Thread.AllocateDataSlot(); static void Main() { Thread.SetData(slot, 123); //輸出本地執行緒TLS資料值 Console.WriteLine(Thread.GetData(slot)); //輸出另一個執行緒TLS資料值 ThreadPool.QueueUserWorkItem(_ => Console.WriteLine(Thread.GetData(slot) == null)); //控制檯等待執行緒結束 Console.ReadKey(); }
輸出:
123 True

第二行是True,那麼另一個執行緒中的資料是null。

最後重點:.NET 4.0後的ThreadLocal會提供預設值的,還記得我上面說的那句話“ThreadLocal的操作或多或少有點像上面的未命名的LocalDataStoreSlot”?有人可能會問那為什麼要創造出ThreadLocal?還有一個很大的區別ThreadLocal可以提供TLS中資料的預設值。(另外還有ThreadLocal是泛型類,而LocalDataStoreSlot不是)。

程式碼:

static ThreadLocal<int> local = new ThreadLocal<int>(() => 123); static void Main() { //輸出本地執行緒TLS資料值 Console.WriteLine(local.Value); //輸出另一個執行緒TLS資料值 ThreadPool.QueueUserWorkItem(_ => Console.WriteLine(local.Value)); //控制檯等待執行緒結束 Console.ReadKey(); }
輸出:
123 123

相關文章