程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C# 線程同步詳解

C# 線程同步詳解

編輯:C#入門知識

C# 線程同步詳解。本站提示廣大學習愛好者:(C# 線程同步詳解)文章只能為提供參考,不一定能成為您想要的結果。以下是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做更多的工作,因此,這種自旋鎖應該用於保護那些執行的非常快的代碼。

以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved