本文主要介紹了C#中執行緒同步的相關知識。具有很好的參考價值,下面跟著小編一起來看下吧
前言
#當線程池的線程阻塞時,線程池會創建額外的線程,而創建、銷毀和調度線程所需要相當昂貴的內存資源,另外,很多的開發人員看見自己程序的線程沒有做任何有用的事情時習慣創建更多的線程,為了構建可伸縮、響應靈敏的程序,我們在前面介紹了C#非同步程式設計詳解
但是非同步程式設計同樣也存在著很嚴重的問題,如果兩個不同的執行緒訪問相同的變數和數據,按照我們非同步函數的實現方式,不可能存在兩個執行緒同時存取相同的數據,這個時候我們需要執行緒同步。當多個執行緒同時存取共享資料的時,執行緒同步能防止資料損壞,之所以強調同時這個概念,因為執行緒同步本質就是計時問題。
非同步和同步是相對的,同步就是順序執行,執行完一個再執行下一個,需要等待、協調運作。非同步是彼此獨立,在等待某事件的過程中繼續做自己的事,不需要等待這一事件完成後再工作。線程就是實作異步的一個方式。非同步是讓呼叫方法的主執行緒不需要同步等待另一個執行緒的完成,從而可以讓主執行緒乾其它的事情。
基元使用者模式與核心模式建構
#基礎概念
基元:可以在程式碼中使用的簡單的建構
使用者模式:透過特殊的CPU指令協調線程,作業系統永遠偵測不到一個執行緒在基元使用者模式的建構上阻塞。
核心模式:由windows本身提供,在應用程式的執行緒中呼叫由核心實現的函數。
使用者模式建構
易變建構
C#編譯器、JIT編譯器和CPU都會對程式碼進行最佳化,它們盡量保證保留我們的意圖,但是從多執行緒的角度出發,我們的意圖不一定會被保留,下面舉例說明:
static void Main(string[] args) { Console.WriteLine("让worker函数运行5s后停止"); var t = new Thread(Worker); t.Start(); Thread.Sleep(5000); stop = true; Console.ReadLine(); } private static bool stop = false; private static void Worker(object obj) { int x = 0; while (!stop) { x++; } Console.WriteLine("worker函数停止x={0}",x); }
編譯器如果檢查到stop為false,就產生程式碼來進入一個無限循環,並且在循環中一直遞增x,所以優化循環很快完成,但是編譯器只偵測stop一次,並不是每次都會偵測。
範例2---兩個執行緒同時存取:
class test { private static int m_flag = 0; private static int m_value = 0; public static void Thread1(object obj) { m_value = 5; m_flag = 1; } public static void Thread2(object obj) { if (m_flag == 1) Console.WriteLine("m_value = {0}", m_value); } //多核CPU机器才会出现线程同步问题 public void Exec() { var thread1 = new Thread(Thread1); var thread2 = new Thread(Thread2); thread1.Start(); thread2.Start(); Console.ReadLine(); } }
#程式在執行的時候,編譯器必須將變數m_flag和m_value從RAM讀入CPU暫存器,RAM先傳遞m_value的值0,thread1把值變成5,但是thread2並不知道thread2仍然認為值為0,這種問題一般來說發生在多核心CPU的機率大一些,應該CPU越多,多個執行緒同時存取資源的幾率就越大。
關鍵字volatile,作用禁止C#編譯器、JTP編譯器和CPU執行的一些最佳化,如果做用於變數後,將不允許欄位快取到CPU的暫存器中,確保欄位的讀寫都在RAM中進行。
互鎖建構
System.Threading.Interlocked類別中的每個方法都執行一次原子的讀取以及寫入操作,呼叫某個Interlocked方法之前的任何變數寫入都在這個Interlocked方法呼叫之前執行,而呼叫之後的任何變數讀取都在這個呼叫之後讀取。
Interlocked方法主要是對INT32變數進行靜態運算Add、Decrement、Compare、Exchange、CompareChange等方法,也接受object、Double等類型的參數。
原子操作:是指不會被執行緒調度機制打斷的操作;這種操作一旦開始,就一直運行到結束,中間不會有任何 context switch (切換到另一個執行緒)。
程式碼示範:
說明:透過Interlocked的方法非同步查詢幾個web伺服器,並同時傳回數據,且結果只執行一次。
//上报状态类型 enum CoordinationStatus { Cancel, Timeout, AllDone }
class AsyncCoordinator { //AllBegun 内部调用JustEnded来递减它 private int _mOpCount = 1; //0=false,1=true private int _mStatusReported = 0; private Action<CoordinationStatus> _mCallback; private Timer _mTimer; //发起一个操作之前调用 public void AboutToBegin(int opsToAdd = 1) { Interlocked.Add(ref _mOpCount, opsToAdd); } //处理好一个操作的结果之后调用 public void JustEnded() { if (Interlocked.Decrement(ref _mOpCount) == 0) { ReportStatus(CoordinationStatus.AllDone); } } //该方法必须在发起所有操作后调用 public void AllBegin(Action<CoordinationStatus> callback, int timeout = Timeout.Infinite) { _mCallback = callback; if (timeout != Timeout.Infinite) { _mTimer = new Timer(TimeExpired, null, timeout, Timeout.Infinite); JustEnded(); } } private void TimeExpired(object o) { ReportStatus(CoordinationStatus.Timeout); } public void Cancel() { ReportStatus(CoordinationStatus.Cancel); } private void ReportStatus(CoordinationStatus status) { //如果状态从未报告过,就报告它,否则就忽略它,只调用一次 if (Interlocked.Exchange(ref _mStatusReported, 1) == 0) { _mCallback(status); } } }#
class MultiWebRequest { //辅助类 用于协调所有的异步操作 private AsyncCoordinator _mac = new AsyncCoordinator(); protected Dictionary<string,object> _mServers = new Dictionary<string, object> { {"http://www.baidu.com",null},{"http://www.Microsoft.com",null},{"http://www.cctv.com",null}, {"http://www.souhu.com",null},{"http://www.sina.com",null},{"http://www.tencent.com",null}, {"http://www.youku.com",null} }; private Stopwatch sp; public MultiWebRequest(int timeout = Timeout.Infinite) { sp = new Stopwatch(); sp.Start(); //通过异步方式一次性发起请求 var httpclient = new HttpClient(); foreach (var server in _mServers.Keys) { _mac.AboutToBegin(1); httpclient.GetByteArrayAsync(server).ContinueWith(task => ComputeResult(server, task)); } _mac.AllBegin(AllDone,timeout); Console.WriteLine(""); } private void ComputeResult(string server, Task<Byte[]> task) { object result; if (task.Exception != null) { result = task.Exception.InnerException; } else { //线程池处理IO result = task.Result.Length; } //保存返回结果的长度 _mServers[server] = result; _mac.JustEnded(); } public void Cancel() { _mac.Cancel(); } private void AllDone(CoordinationStatus status) { sp.Stop(); Console.WriteLine("响应耗时总计{0}",sp.Elapsed); switch (status) { case CoordinationStatus.Cancel: Console.WriteLine("操作取消"); break; case CoordinationStatus.AllDone: Console.WriteLine("操作完成,完成的结果如下"); foreach (var server in _mServers) { Console.WriteLine("{0}",server.Key); object result = server.Value; if (result is Exception) { Console.WriteLine("错误原因{0}",result.GetType().Name); } else { Console.WriteLine("返回字节数为:{0}",result); } } break; case CoordinationStatus.Timeout: Console.WriteLine("操作超时"); break; default: throw new ArgumentOutOfRangeException("status", status, null); } } }
非常建議大家參考一下以上程式碼,我在對伺服器進行存取時,也會常常參考這個模型。
簡單的自旋鎖定
class SomeResource { private SimpleSpinLock s1 = new SimpleSpinLock(); public void AccessResource() { s1.Enter(); //一次是有一个线程才能进入访问 s1.Leave(); } } class SimpleSpinLock { private int _mResourceInUse; public void Enter() { while (true) { if(Interlocked.Exchange(ref _mResourceInUse,1)==0) return; } } public void Leave() { Volatile.Write(ref _mResourceInUse,1); } }
這就是一個執行緒同步鎖定的簡單實現,這種鎖定的最大問題在於,存在競爭的情況下會造成線程的“自旋”,這會浪費CPU的寶貴時間,組織CPU做更多的工作,因此,這種自旋鎖應該用於保護那些執行的非常快的代碼。
以上就是C#中 執行緒同步程式碼詳細介紹的內容,更多相關內容請關注PHP中文網(www.php.cn)!