在線程裏,若是須要共享數據,那麼必定須要使用同步技術,確保一次只有一個線程訪問和改變共享數據的狀態。在.net中,lock語句、Interlocked類和Monitor類可用於進程內部的同步。安全
lock語句是設置鎖定和解除鎖定的一種簡單方式。在使用lock語句以前,先進入另外一個爭用條件。例如:this
public class SharedState { public int State { get; set; } } public class Job { SharedState sharedState; public Job(SharedState sharedState) { this.sharedState = sharedState; } public void DoTheJob() { for (int i = 0; i < 50000; i++) { sharedState.State += 1; } } } static void Main() { int numTasks = 20; var state = new SharedState(); var tasks = new Task[numTasks];//定義20個任務 for (int i = 0; i < numTasks; i++) { tasks[i] = Task.Run(() => new Job(state).DoTheJob());//啓動20個任務,同時對數據進行修改 } for (int i = 0; i < numTasks; i++) { tasks[i].Wait();//等待全部任務結束 } Console.WriteLine("summarized {0}", state.State);//預想應該輸出:summarized 1000000 }
實際上的輸出與預想輸出並不一致,每次運行的輸出結果都不一樣,但沒有一個是正確的。若是將線程數量減小,那麼獲得正確值的次數會增多,但也不是每次都正確。spa
使用lock關鍵字,能夠實現多個線程訪問同一個數據時的同步問題。lock語句表示等待指定對象的鎖定,該對象只能時引用類型。進行鎖定後——只鎖定了一個線程,就運行lock語句塊中的代碼,在lock塊最後接觸鎖定,以便另外一個線程能夠鎖定該對象。.net
lock(obj) { //執行代碼 } //鎖定靜態成員,能夠因此其類型(object) lock(typeof(StaticCalss)) { //執行代碼 }
因此修改以上的代碼,使用SyncRoot模式。可是,若是是對屬性的訪問進行鎖定:線程
public class SharedState { private object syncRoot = new object(); private int state = 0; public int State { get { lock (syncRoot) return state; } set { lock (syncRoot) state = value; } } }
仍會出現前面的爭用狀況。在方法調用get存儲器,以得到state的當前值,而後set存儲器給state設置新值。在調用對象的get和set存儲器期間,對象並無鎖定,另外一個線程仍然能夠得到臨時值。最好的方法是在不改變SharedState類的前提下,在調用方法中,將lock語句添加到合適的地方:code
public class SharedState { public int State { get; set; } } public class Job { SharedState sharedState; public Job(SharedState sharedState) { this.sharedState = sharedState; } public void DoTheJob() { for (int i = 0; i < 50000; i++) { lock (sharedState) { sharedState.State += 1; } } } }
在一個地方使用lock語句並不意味着訪問對象的其餘線程都在等待。必須對每一個訪問共享數據的線程顯示使用同步功能。對象
爲使對state的修改做爲一個原子操做,修改代碼:blog
public class SharedState { private int state = 0; public int State { get { return state; } } public int IncrementState() { lock (this) { return ++state; } } } //外部訪問 public void DoTheJob() { for (int i = 0; i < 50000; i++) { sharedState.IncrementState(); } }
Interlocked類用於使變量的簡單語句原子化。i++並不是線程安全的,它涉及三個步驟:取值、自增、存值。這些操做可能被線程調度器打斷。Interlocked類提供了以線程安全的方式遞增、遞減、交換和讀取值的方法。Interlocked類只能用於簡單的同步問題,並且很快。所以,上面的IncrementState()方法的代碼能夠改成:return Interlocked.Increment(ref state);進程
lcok語句最終會有C#編譯器解析爲使用Monitor類。rem
lock(obj) { //執行代碼 }
簡單的lock(obj)語句會被解析爲調用Enter()方法,該方法會一直等待,直到線程鎖定對象。一次只有一個線程能鎖定對象,只要解除鎖定,線程就能夠進入同步階段。Monitor類的Exit()方法解除鎖定。編譯器把Exit()方法放在try塊的finally中,不管是否拋出異常,都將在語句塊運行末尾解除鎖定。
Monitor.Enter(obj); try { //執行代碼 } finally { Monitor.Exit(obj); }
相對於lock語句,Mpnitor類能夠設置一個等待被鎖定的超時值。這樣就不會無限期的等待鎖定,若是等待鎖定時間超過規定時間,則返回false,表示未被鎖定,線程再也不等待,執行其餘操做。也許之後,該線程會再次嘗試得到鎖定:
bool lockTaken = false; Monitor.TryEnter(obj,500, ref lockTaken);//在500ms內,是否鎖定了對象 if (lockTaken) { try { //執行代碼 } finally { Monitor.Exit(obj); } } else { //未得到鎖定,執行代碼 }
若是基於對象的鎖定對象(Monitor)的系統開銷因爲垃圾回收而太高,能夠使用SpinLock結構。,SpinLock結構適用於:有大量的鎖定,並且鎖定時間老是很是短的狀況。應避免使用多個SpinLock結構,也不要調用任何可能阻塞的內容。