[英]How to make a static variable thread-safe
我有這個靜態類,它包含一個靜態變量(一個簡單的int)。 我已經在線程的Run()
方法中實現了一個lock()
,因此沒有其他線程可以同時訪問這個類,但變量仍然會變得瘋狂,顯示重復,極其高的值等。
這是班級:
public static class ExplorationManager
{
public static int Counter = 0;
public static void ExplorerMaker(List<int[]> validPaths, List<string> myParents, string[,] myExplorationMap, List<int[]> myPositions)
{
foreach (var thread in validPaths.Select
(path => new Explorer(myParents, path, myExplorationMap, myPositions)).
Select(explorer => new Thread(explorer.Explore)))
{
thread.Name = "Thread of " + Counter + " generation";
Counter++;
thread.Start();
}
}
}
有沒有辦法讓這個變量“更”線程安全?
為了提高此類安全性,您需要解決至少2個問題。
第一個是讓Counter
private
。 在它的當前形式中,變量是100%公開的,並且它可以被應用程序中的任何代碼片段變異。 今天它可能是安全的,但沒有什么能保護你明天犯錯。 如果您仍希望其他代碼段能夠讀取屬性,則使用訪問器
private static int m_counter;
public static int Counter {
get { return m_counter; }
}
第二個問題是++
不是在線程之間共享的位置上的安全操作。 它擴展為以下代碼
Counter = Counter + 1;
這實際上在做什么
幾乎任何時候都可以中斷線程。 如果一個線程在步驟1,2或3中斷,另一個線程完全執行序列,那么您將最終添加/存儲過時值。 這就是++
不安全的原因。 在線程之間增加共享值的安全方法是使用Interlocked.Increment
。 它的設計正是為了這個目的
Interlocked.Increment(ref m_counter);
使用Interlocked類:
Interlocked.Increment(ref Counter);
您需要在靜態變量的所有讀/寫周圍使用lock
。 就像是:
public static readonly object CounterLock = new object();
...
lock ( CounterLock )
{
Counter++;
}
...
關鍵是所有讀/寫必須受到鎖的保護 - 它不足以保護單個位置,因為當其他地方的鎖生效時,執行讀或寫的線程仍然可能會發生變化。
鎖保護代碼區域而不是變量,這就是為什么在訪問共享變量時需要鎖定的原因。
請注意,您無法鎖定Counter
變量 - 您需要引用類型的實例作為鎖,而不是值類型。 這就是我使用object
作為鎖類型的原因(另一個答案也是如此)。
像這樣的東西應該做的伎倆:
public static class ExplorationManager
{
public static int Counter = 0;
private static object _lock = new object();
public static void ExplorerMaker(List<int[]> validPaths, List<string> myParents, string[,] myExplorationMap, List<int[]> myPositions)
{
foreach (var thread in validPaths.Select
(path => new Explorer(myParents, path, myExplorationMap, myPositions)).
Select(explorer => new Thread(explorer.Explore)))
{
thread.Name = "Thread of " + Counter + " generation";
lock(_lock)
{
Counter++;
thread.Start();
}
}
}
您可以嘗試使用靜態構造函數來初始化靜態變量。 最佳做法是提供單獨的locking
對象,以便您可以很好地控制鎖的粒度。
Interlocked.Increment是另一個線程安全選項。 如果你只是需要反擊,那么使用起來很簡單。
var newCounter = Interlocked.Increment(ref Counter)
thread.Name = "Thread of " + (newCounter-1) + " generation";
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.