天天看點

多線程中Local Store Slot(本地存儲槽)

在Java中有一種ThreadLocal機制,為每一個使用該變量的線程都提供一個變量值的副本,是每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本沖突。從線程的角度看,就好像每一個線程都完全擁有該變量。比如在Hibernate中使用Session的時候,因為Session是線程不安全的,是以要考慮并發問題。而使用ThreadLocal的話,會在每個線程中有一個Session的副本,是以就不會有線程沖突的問題。

.NET中也有相應的機制,來實作變量的線程局部化,而且有多種方法。

1. 使用ThreadStatic特性

ThreadStatic特性是最簡單的TLS使用,且隻支援靜态字段,隻需要在字段上标記這個特性就可以了:

//TLS中的str變量
[ThreadStatic]
static string str = "hehe";
 
static void Main()
{
    //另一個線程隻會修改自己TLS中的str變量
    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

顯然本地線程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

繼續閱讀