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

c#線程同步應用詳解示例

編輯:C#入門知識

c#線程同步應用詳解示例。本站提示廣大學習愛好者:(c#線程同步應用詳解示例)文章只能為提供參考,不一定能成為您想要的結果。以下是c#線程同步應用詳解示例正文


在運用法式中應用多個線程的一個利益是每一個線程都可以異步履行。關於 Windows 運用法式,耗時的義務可以在後台履行,而使運用法式窗口和控件堅持呼應。關於辦事器運用法式,多線程處置供給了用分歧線程處置每一個傳入要求的才能。不然,在完整知足前一個要求之前,將沒法處置每一個新要求。但是,線程的異步特征意味著必需調和對資本(如文件句柄、收集銜接和內存)的拜訪。不然,兩個或更多的線程能夠在統一時光拜訪雷同的資本,而每一個線程都不曉得其他線程的操作。

線程同步的方法

線程同步有:臨界區、互斥區、事宜、旌旗燈號量四種方法
臨界區(Critical Section)、互斥量(Mutex)、旌旗燈號量(Semaphore)、事宜(Event)的差別
1、臨界區:經由過程對多線程的串行化來拜訪公共資本或一段代碼,速度快,合適掌握數據拜訪。在隨意率性時辰只許可一個線程對同享資本停止拜訪,假如有多個線程試圖拜訪公共資本,那末在有一個線程進入後,其他試圖拜訪公共資本的線程將被掛起,並一向比及進入臨界區的線程分開,臨界區在被釋放後,其他線程才可以搶占。
2、互斥量:采取互斥對象機制。 只要具有互斥對象的線程才有拜訪公共資本的權限,由於互斥對象只要一個,所以能包管公共資本不會同時被多個線程拜訪。互斥不只能完成統一運用法式的公共資本平安同享,還能完成分歧運用法式的公共資本平安同享
3、旌旗燈號量:它許可多個線程在統一時辰拜訪統一資本,然則須要限制在統一時辰拜訪此資本的最年夜線程數量
4、事 件: 經由過程告訴操作的方法來堅持線程的同步,還可以便利完成對多個線程的優先級比擬的操作

C#中罕見線程同步辦法

我們引見幾種經常使用的C#停止線程同步的方法,這些方法可以依據其道理,找到對應下面的四品種型之一。

1、Interlocked
為多個線程同享的變量供給原子操作。

依據經歷,那些須要在多線程情形下被掩護的資本平日是整型值,且這些整型值在多線程下最多見的操作就是遞增、遞加或相加操作。Interlocked類供給了一個專門的機制用於完成這些特定的操作。這個類供給了Increment、Decrement、Add靜態辦法用於對int或long型變量的遞增、遞加或相加操作。此類的辦法可以避免能夠鄙人列情形產生的毛病:籌劃法式在某個線程正在更新可由其他線程拜訪的變量時切換高低文;或許當兩個線程在分歧的處置器上並發履行時。 此類的成員不激發異常。

Increment和Decrement辦法遞增或遞加變量並將成果值存儲在單個操作中。 在年夜多半盤算機上,增長變量操作不是一個原子操作,須要履行以下步調:

1)將實例變量中的值加載到存放器中。
2)增長或削減該值。
3)在實例變量中存儲該值。
假如不應用 Increment 和 Decrement,線程會在履行完前兩個步調後被爭先。 然後由另外一個線程履行一切三個步調。 當第一個線程從新開端履行時,它籠罩實例變量中的值,形成第二個線程履行增減操作的成果喪失。

Exchange 辦法主動交流指定變量的值。 CompareExchange 辦法組合了兩個操作:比擬兩個值和依據比擬的成果將第三個值存儲在個中一個變量中。 比擬和交流操作按原子操作履行。

案例剖析:同享打印機。

平日我們會應用同享打印機,幾台盤算機同享一台打印機,每台盤算機可以收回打印指令,能夠會湧現並發情形。固然我們曉得,打印機采取了隊列技巧。為了簡化操作,我們假定,在打印機收到敕令時,便可打印,並且在統一時光只能有一個打印義務在履行。我們應用Interlocked辦法來完成多線程同步。詳細代碼以下:


using System;
using System.Threading;

namespace MutiThreadSample.ThreadSynchronization
{
    class PrinterWithInterlockTest
    {
   /// <summary>
   /// 正在應用的打印機
   /// 0代表未應用,1代表正在應用
   /// </summary>
   public static int UsingPrinter = 0;
   /// <summary>
   /// 盤算機數目
   /// </summary>
   public static readonly int ComputerCount = 3;
   /// <summary>
   /// 測試
   /// </summary>
   public static void TestPrint()
   {
  Thread thread;
  Random random = new Random();
  for (int i = 0; i < ComputerCount; i++)
  {
 thread = new Thread(MyThreadProc);
 thread.Name = string.Format("Thread{0}",i);
 Thread.Sleep(random.Next(3));
 thread.Start();
  }
   }
   /// <summary>
   /// 線程履行操作
   /// </summary>
   private static void MyThreadProc()
   {
  //應用打印機停止打印
  UsePrinter();
  //以後線程期待1秒
  Thread.Sleep(1000);
   }
   /// <summary>
   /// 應用打印機停止打印
   /// </summary>
   private static bool UsePrinter()
   {
  //檢討年夜引進能否在應用,假如原始值為0,則為未應用,可以停止打印,不然不克不及打印,持續期待
  if (0 == Interlocked.Exchange(ref UsingPrinter, 1))
  {
 Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);

 //Code to access a resource that is not thread safe would go here.

 //Simulate some work
 Thread.Sleep(500);

 Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);

 //釋放打印機
 Interlocked.Exchange(ref UsingPrinter, 0);
 return true;
  }
  else
  {
 Console.WriteLine("   {0} was denied the lock", Thread.CurrentThread.Name);
 return false;
  }
   }

    }
}

2、lock 症結字

lock 症結字將語句塊標志為臨界區,辦法是獲得給定對象的互斥鎖,履行語句,然後釋放該鎖。
lock 確保當一個線程位於代碼的臨界區時,另外一個線程不進入臨界區。假如其他線程試圖進入鎖定的代碼,則它將一向期待(即被阻攔),直到該對象被釋放。


public void Function()
{
 System.Object locker= new System.Object();
 lock(locker)
{
 // Access thread-sensitive resources.
}
}

lock 挪用塊開端地位的 Enter 和塊停止地位的 Exit。

供給給 lock 症結字的參數必需為基於援用類型的對象,該對象用來界說鎖的規模。在上例中,鎖的規模限制為此函數,由於函數外不存在任何對該對象的援用。嚴厲地說,供給給 lock 的對象只是用來獨一地標識由多個線程同享的資本,所以它可所以隨意率性類實例。但是,現實上,此對象平日表現須要停止線程同步的資本。例如,假如一個容器對象將被多個線程應用,則可以將該容器傳遞給 lock,而 lock 前面的同步代碼塊將拜訪該容器。只需其他線程在拜訪該容器前先鎖定該容器,則對該對象的拜訪將是平安同步的。平日,最好防止鎖定 public 類型或鎖定不受運用法式掌握的對象實例,例如,假如該實例可以被地下拜訪,則 lock(this) 能夠會有成績,由於不受掌握的代碼也能夠會鎖定該對象。這能夠招致逝世鎖,即兩個或更多個線程期待釋放統一對象。出於異樣的緣由,鎖定公共數據類型(比擬於對象)也能夠招致成績。鎖定字符串特別風險,由於字符串被公共說話運轉庫 (CLR)“暫留”。這意味著全部法式中任何給定字符串都只要一個實例,就是這統一個對象表現了一切運轉的運用法式域的一切線程中的該文本。是以,只需在運用法式過程中的任何地位處具有雷同內容的字符串上放置了鎖,就將鎖定運用法式中該字符串的一切實例。是以,最好鎖定不會被暫留的公有或受掩護成員。某些類供給專門用於鎖定的成員。例如,Array 類型供給 SyncRoot。很多聚集類型也供給 SyncRoot。

罕見的構造 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 違背此原則:

1)假如實例可以被公共拜訪,將湧現 lock (this) 成績。
2)假如 MyType 可以被公共拜訪,將湧現 lock (typeof (MyType)) 成績。
3)因為過程中應用統一字符串的任何其他代碼將同享統一個鎖,所以湧現 lock(“myLock”) 成績。
最好做法是界說 private 對象來鎖定, 或 private static 對象變量來掩護一切實例所共有的數據。關於鎖的研討,年夜家可以參考:

案例剖析:持續應用同享打印機的案例

我們只需對後面的例子稍作修正便可完成lock停止同步。

聲明鎖對象:


/// <summary>
/// 正在應用的打印機
/// </summary>
private static object UsingPrinterLocker = new object();

將打印辦法修正以下:


/// <summary>
/// 應用打印機停止打印
/// </summary>
private static void UsePrinter()
{
  //臨界區
  lock (UsingPrinterLocker)
  {
 Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
 //模仿打印操作
 Thread.Sleep(500);
 Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
  }
}

3、監督器

與 lock 症結字相似,監督器避免多個線程同時履行代碼塊。Enter 辦法許可一個且僅一個線程持續履行前面的語句;其他一切線程都將被阻攔,直到履行語句的線程挪用 Exit。這與應用 lock 症結字一樣。現實上,lock 症結字就是用 Monitor 類來完成的。例如:(持續修正同享打印機案例,增長辦法UsePrinterWithMonitor)


/// <summary>
/// 應用打印機停止打印
/// </summary>
private static void UsePrinterWithMonitor()
{
  System.Threading.Monitor.Enter(UsingPrinterLocker);
  try
  {
 Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
 //模仿打印操作
 Thread.Sleep(500);
 Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
  }
  finally
  {
 System.Threading.Monitor.Exit(UsingPrinterLocker);
  }
}

 應用 lock 症結字平日比直接應用 Monitor 類更可取,一方面是由於 lock 更簡練,另外一方面是由於 lock 確保了即便受掩護的代碼激發異常,也能夠釋放基本監督器。這是經由過程 finally 症結字來完成的,不管能否激發異常它都履行聯系關系的代碼塊。

4、同步事宜和期待句柄

應用鎖或監督器關於避免同時履行辨別線程的代碼塊很有效,然則這些結構不許可一個線程向另外一個線程轉達事宜。這須要“同步事宜”,它是有兩個狀況(終止和非終止)的對象,可以用來激活和掛起線程。讓線程期待非終止的同步事宜可以將線程掛起,將事宜狀況更改成終止可以將線程激活。假如線程試圖期待曾經終止的事宜,則線程將持續履行,而不會延遲。

同步事宜有兩種:AutoResetEvent和ManualResetEvent。它們之間獨一的分歧在於,不管什麼時候,只需AutoResetEvent激活線程,它的狀況將主動從終止變成非終止。相反,ManualResetEvent許可它的終止狀況激活隨意率性多個線程,只要當它的Reset辦法被挪用時才復原到非終止狀況。

期待句柄,可以經由過程挪用一種期待辦法,如WaitOne、WaitAny或WaitAll,讓線程期待事宜。System.Threading.WaitHandle.WaitOne使線程一向期待,直到單個事宜變成終止狀況;System.Threading.WaitHandle.WaitAny阻攔線程,直到一個或多個指導的事宜變成終止狀況;System.Threading.WaitHandle.WaitAll阻攔線程,直到一切指導的事宜都變成終止狀況。當挪用事宜的Set辦法時,事宜將變成終止狀況。

AutoResetEvent許可線程經由過程發旌旗燈號相互通訊。平日,當線程須要獨有拜訪資本時應用該類。線程經由過程挪用AutoResetEvent上的WaitOne來期待旌旗燈號。假如AutoResetEvent為非終止狀況,則線程會被阻攔,並期待以後掌握資本的線程經由過程挪用Set來告訴資本可用。挪用Set向AutoResetEvent發旌旗燈號以釋放期待線程。AutoResetEvent將堅持終止狀況,直到一個正在期待的線程被釋放,然後主動前往非終止狀況。假如沒有任何線程在期待,則狀況將無窮期地堅持為終止狀況。假如當AutoResetEvent為終止狀況時線程挪用WaitOne,則線程不會被阻攔。AutoResetEvent將立刻釋放線程並前往到非終止狀況。
可以經由過程將一個布爾值傳遞給結構函數來掌握AutoResetEvent的初始狀況:假如初始狀況為終止狀況,則為true;不然為false。
AutoResetEvent也能夠同staticWaitAll和WaitAny辦法一路應用。
案例:

案例引見:

明天我們來做飯,做飯呢,須要一菜、一粥。明天我們吃魚。

熬粥和做魚,是比擬龐雜的任務流程,
做粥:選材、淘米、熬制
做魚:洗魚、切魚、腌制、烹饪
為了進步效力,我們用兩個線程來預備這頓飯,然則,如今只要一口鍋,只能等一個做完以後,另外一個能力停止最初的烹饪。

來看實例代碼:


usingSystem;
usingSystem.Threading;

namespaceMutiThreadSample.ThreadSynchronization
{
///<summary>
///案例:做飯
///明天的Dinner預備吃魚,還要熬粥
///熬粥和做魚,是比擬龐雜的任務流程,
///做粥:選材、淘米、熬制
///做魚:洗魚、切魚、腌制、烹饪
///我們用兩個線程來預備這頓飯
///然則,如今只要一口鍋,只能等一個做完以後,另外一個能力停止最初的烹饪
///</summary>
classCookResetEvent
{
///<summary>
///
///</summary>
privateAutoResetEventresetEvent=newAutoResetEvent(false);
///<summary>
///做飯
///</summary>
publicvoidCook()
{
ThreadporridgeThread=newThread(newThreadStart(Porridge));
porridgeThread.Name="Porridge";
porridgeThread.Start();

ThreadmakeFishThread=newThread(newThreadStart(MakeFish));
makeFishThread.Name="MakeFish";
makeFishThread.Start();

//期待5秒
Thread.Sleep(5000);

resetEvent.Reset();
}
///<summary>
///熬粥
///</summary>
publicvoidPorridge()
{
//選材
Console.WriteLine("Thread:{0},開端選材",Thread.CurrentThread.Name);

//淘米
Console.WriteLine("Thread:{0},開端淘米",Thread.CurrentThread.Name);

//熬制
Console.WriteLine("Thread:{0},開端熬制,須要2秒鐘",Thread.CurrentThread.Name);
//須要2秒鐘
Thread.Sleep(2000);
Console.WriteLine("Thread:{0},粥曾經做好,鍋閒了",Thread.CurrentThread.Name);

resetEvent.Set();
}
///<summary>
///做魚
///</summary>
publicvoidMakeFish()
{
//洗魚
Console.WriteLine("Thread:{0},開端洗魚",Thread.CurrentThread.Name);

//腌制
Console.WriteLine("Thread:{0},開端腌制",Thread.CurrentThread.Name);

//期待鍋余暇出來
resetEvent.WaitOne();

//烹饪
Console.WriteLine("Thread:{0},終究有鍋了",Thread.CurrentThread.Name);
Console.WriteLine("Thread:{0},開端做魚,須要5秒鐘",Thread.CurrentThread.Name);
Thread.Sleep(5000);
Console.WriteLine("Thread:{0},魚做好了,好噴鼻",Thread.CurrentThread.Name);

resetEvent.Set();
}
}
}

ManualResetEvent與AutoResetEvent用法根本相似,這裡不多做引見。

5、Mutex對象

mutex與監督器相似;它避免多個線程在某一時光同時履行某個代碼塊。現實上,稱號“mutex”是術語“相互排擠(mutuallyexclusive)”的簡寫情勢。但是與監督器分歧的是,mutex可以用來使跨過程的線程同步。mutex由Mutex類表現。當用於過程間同步時,mutex稱為“定名mutex”,由於它將用於另外一個運用法式,是以它不克不及經由過程全局變量或靜態變量同享。必需給它指定一個稱號,能力使兩個運用法式拜訪統一個mutex對象。
雖然mutex可以用於過程內的線程同步,然則應用Monitor平日更加可取,由於監督器是專門為.NETFramework而設計的,因此它可以更好天時用資本。比擬之下,Mutex類是Win32結構的包裝。雖然mutex比監督器更加壯大,然則絕對於Monitor類,它所須要的互操作轉換更消費盤算資本。

當地mutex和體系mutex
Mutex分兩品種型:當地mutex和定名體系mutex。假如應用接收稱號的結構函數創立了Mutex對象,那末該對象將與具有該稱號的操作體系對象相干聯。定名的體系mutex在全部操作體系中都可見,而且可用於同步過程運動。您可以創立多個Mutex對象來表現統一定名體系mutex,並且您可使用OpenExisting辦法翻開現有的定名體系mutex。
當地mutex僅存在於過程傍邊。過程中援用當地Mutex對象的隨意率性線程都可使用當地mutex。每一個Mutex對象都是一個零丁的當地mutex。

在當地Mutex中,用法與Monitor根本分歧

持續修正後面的打印機案例:

聲明Mutex對象:


///<summary>
///mutex對象
///</summary>
privatestaticMutexmutex=newMutex();

詳細操作:


///<summary>
///應用打印機停止打印
///</summary>
privatestaticvoidUsePrinterWithMutex()
{
mutex.WaitOne();
try
{
Console.WriteLine("{0}acquiredthelock",Thread.CurrentThread.Name);
//模仿打印操作
Thread.Sleep(500);
Console.WriteLine("{0}exitinglock",Thread.CurrentThread.Name);
}
finally
{
mutex.ReleaseMutex();
}
}

多線程挪用:


///<summary>
///測試
///</summary>
publicstaticvoidTestPrint()
{
Threadthread;
Randomrandom=newRandom();
for(inti=0;i<ComputerCount;i++)
{
thread=newThread(MyThreadProc);
thread.Name=string.Format("Thread{0}",i);
Thread.Sleep(random.Next(3));
thread.Start();
}
}
///<summary>
///線程履行操作
///</summary>
privatestaticvoidMyThreadProc()
{
//應用打印機停止打印
//UsePrinter();
//monitor同步
//UsePrinterWithMonitor();
//用Mutex同步
UsePrinterWithMutex();
//以後線程期待1秒
Thread.Sleep(1000);
}

最初的打印機案例代碼:


usingSystem;
usingSystem.Threading;

namespaceMutiThreadSample.ThreadSynchronization
{
classPrinterWithLockTest
{
///<summary>
///正在應用的打印機
///</summary>
privatestaticobjectUsingPrinterLocker=newobject();
///<summary>
///盤算機數目
///</summary>
publicstaticreadonlyintComputerCount=3;
///<summary>
///mutex對象
///</summary>
privatestaticMutexmutex=newMutex();
///<summary>
///測試
///</summary>
publicstaticvoidTestPrint()
{
Threadthread;
Randomrandom=newRandom();
for(inti=0;i<ComputerCount;i++)
{
thread=newThread(MyThreadProc);
thread.Name=string.Format("Thread{0}",i);
Thread.Sleep(random.Next(3));
thread.Start();
}
}
///<summary>
///線程履行操作
///</summary>
privatestaticvoidMyThreadProc()
{
//應用打印機停止打印
//UsePrinter();
//monitor同步
//UsePrinterWithMonitor();
//用Mutex同步
UsePrinterWithMutex();
//以後線程期待1秒
Thread.Sleep(1000);
}
///<summary>
///應用打印機停止打印
///</summary>
privatestaticvoidUsePrinter()
{
//臨界區
lock(UsingPrinterLocker)
{
Console.WriteLine("{0}acquiredthelock",Thread.CurrentThread.Name);
//模仿打印操作
Thread.Sleep(500);
Console.WriteLine("{0}exitinglock",Thread.CurrentThread.Name);
}
}

///<summary>
///應用打印機停止打印
///</summary>
privatestaticvoidUsePrinterWithMonitor()
{
System.Threading.Monitor.Enter(UsingPrinterLocker);
try
{
Console.WriteLine("{0}acquiredthelock",Thread.CurrentThread.Name);
//模仿打印操作
Thread.Sleep(500);
Console.WriteLine("{0}exitinglock",Thread.CurrentThread.Name);
}
finally
{
System.Threading.Monitor.Exit(UsingPrinterLocker);
}
}

///<summary>
///應用打印機停止打印
///</summary>
privatestaticvoidUsePrinterWithMutex()
{
mutex.WaitOne();
try
{
Console.WriteLine("{0}acquiredthelock",Thread.CurrentThread.Name);
//模仿打印操作
Thread.Sleep(500);
Console.WriteLine("{0}exitinglock",Thread.CurrentThread.Name);
}
finally
{
mutex.ReleaseMutex();
}
}
}
}

6、讀取器/編寫器鎖

ReaderWriterLockSlim類許可多個線程同時讀取一個資本,但在向該資本寫入時請求線程期待以取得獨有鎖。

可以在運用法式中應用ReaderWriterLockSlim,以便在拜訪一個同享資本的線程之間供給調和同步。取得的鎖是針對ReaderWriterLockSlim自己的。
設計您運用法式的構造,讓讀取和寫入操作的時光盡量最短。由於寫入鎖是排他的,所以長時光的寫入操作會直接影響吞吐量。長時光的讀取操作會阻攔處於期待狀況的編寫器,而且,假如至多有一個線程在期待寫入拜訪,則要求讀取拜訪的線程也將被阻攔。

案例:結構一個線程平安的緩存


usingSystem;
usingSystem.Threading;
usingSystem.Collections.Generic;


namespaceMutiThreadSample.ThreadSynchronization
{
///<summary>
///同步Cache
///</summary>
publicclassSynchronizedCache
{
privateReaderWriterLockSlimcacheLock=newReaderWriterLockSlim();
privateDictionary<int,string>innerCache=newDictionary<int,string>();
///<summary>
///讀取
///</summary>
///<paramname="key"></param>
///<returns></returns>
publicstringRead(intkey)
{
cacheLock.EnterReadLock();
try
{
returninnerCache[key];
}
finally
{
cacheLock.ExitReadLock();
}
}
///<summary>
///添加項
///</summary>
///<paramname="key"></param>
///<paramname="value"></param>
publicvoidAdd(intkey,stringvalue)
{
cacheLock.EnterWriteLock();
try
{
innerCache.Add(key,value);
}
finally
{
cacheLock.ExitWriteLock();
}
}
///<summary>
///添加項,有超時限制
///</summary>
///<paramname="key"></param>
///<paramname="value"></param>
///<paramname="timeout"></param>
///<returns></returns>
publicboolAddWithTimeout(intkey,stringvalue,inttimeout)
{
if(cacheLock.TryEnterWriteLock(timeout))
{
try
{
innerCache.Add(key,value);
}
finally
{
cacheLock.ExitWriteLock();
}
returntrue;
}
else
{
returnfalse;
}
}
///<summary>
///添加或許更新
///</summary>
///<paramname="key"></param>
///<paramname="value"></param>
///<returns></returns>
publicAddOrUpdateStatusAddOrUpdate(intkey,stringvalue)
{
cacheLock.EnterUpgradeableReadLock();
try
{
stringresult=null;
if(innerCache.TryGetValue(key,outresult))
{
if(result==value)
{
returnAddOrUpdateStatus.Unchanged;
}
else
{
cacheLock.EnterWriteLock();
try
{
innerCache[key]=value;
}
finally
{
cacheLock.ExitWriteLock();
}
returnAddOrUpdateStatus.Updated;
}
}
else
{
cacheLock.EnterWriteLock();
try
{
innerCache.Add(key,value);
}
finally
{
cacheLock.ExitWriteLock();
}
returnAddOrUpdateStatus.Added;
}
}
finally
{
cacheLock.ExitUpgradeableReadLock();
}
}
///<summary>
///刪除項
///</summary>
///<paramname="key"></param>
publicvoidDelete(intkey)
{
cacheLock.EnterWriteLock();
try
{
innerCache.Remove(key);
}
finally
{
cacheLock.ExitWriteLock();
}
}
///<summary>
///
///</summary>
publicenumAddOrUpdateStatus
{
Added,
Updated,
Unchanged
};
}
}

7、Semaphore和SemaphoreSlim

System.Threading.Semaphore類表現一個定名(體系規模)旌旗燈號量或當地旌旗燈號量。它是一個對Win32旌旗燈號量對象的精簡包裝。Win32旌旗燈號量是計數旌旗燈號量,可用於掌握對資本池的拜訪。
SemaphoreSlim類表現一個輕量的疾速旌旗燈號量,可用於在一個估計期待時光會異常短的過程內停止期待。SemaphoreSlim會盡量多地依附由公共說話運轉時(CLR)供給的同步基元。然則,它也會依據須要供給延遲初始化的、基於內核的期待句柄,以支撐期待多個旌旗燈號量。SemaphoreSlim還支撐應用撤消標志,但它不支撐定名旌旗燈號量或應用期待句柄來停止同步。

線程經由過程挪用WaitOne辦法來進入旌旗燈號量,此辦法是從WaitHandle類派生的。當挪用前往時,旌旗燈號量的計數將削減。當一個線程要求項而計數為零時,該線程會被阻攔。當線程經由過程挪用Release辦法釋放旌旗燈號量時,將許可被阻攔的線程進入。其實不包管被壅塞的線程進入旌旗燈號量的次序,例如先輩先出(FIFO)或落後先出(LIFO)。旌旗燈號量的計數在每次線程進入旌旗燈號量時減小,在線程釋放旌旗燈號量時增長。當計數為零時,前面的要求將被壅塞,直到有其他線程釋放旌旗燈號量。當一切的線程都已釋放旌旗燈號量時,計數到達創立旌旗燈號量時所指定的最年夜值。

案例剖析:購置火車票

還得列隊停止購置,購置窗口是無限的,只要窗口余暇時能力購置


usingSystem;
usingSystem.Threading;

namespaceMutiThreadSample.ThreadSynchronization
{
///<summary>
///案例:付出流程
///如超市、藥店、火車票等,都無限定的幾個窗口停止結算,只要有窗口余暇,能力停止結算。
///我們就用多線程來模仿結算進程
///</summary>
classPaymentWithSemaphore
{
///<summary>
///聲明收銀員總數為3個,然則以後余暇的個數為0,能夠還沒開端下班。
///</summary>
privatestaticSemaphoreIdleCashiers=newSemaphore(0,3);
///<summary>
///測試付出進程
///</summary>
publicstaticvoidTestPay()
{
ParameterizedThreadStartstart=newParameterizedThreadStart(Pay);
//假定同時有5小我來買票
for(inti=0;i<5;i++)
{
Threadthread=newThread(start);
thread.Start(i);
}

//主線程期待,讓一切的的線程都激活
Thread.Sleep(1000);
//釋放旌旗燈號量,2個收銀員開端下班了或許有兩個余暇出來了
IdleCashiers.Release(2);
}
///<summary>
///
///</summary>
///<paramname="obj"></param>
publicstaticvoidPay(objectobj)
{
Console.WriteLine("Thread{0}beginsandwaitsforthesemaphore.",obj);
IdleCashiers.WaitOne();
Console.WriteLine("Thread{0}startstoPay.",obj);
//結算
Thread.Sleep(2000);
Console.WriteLine("Thread{0}:Thepaymenthasbeenfinished.",obj);

Console.WriteLine("Thread{0}:Releasethesemaphore.",obj);
IdleCashiers.Release();
}
}
}

8、妨礙(Barrier)4.0後技巧

使多個義務可以或許采取並行方法根據某種算法在多個階段中協同任務。
經由過程在一系列階段間挪動來協作完成一組義務,此時該組中的每一個義務發旌旗燈號指出它曾經達到指定階段的Barrier而且黑暗期待其他義務達到。雷同的Barrier可用於多個階段。

9、SpinLock(4.0後)
SpinLock構造是一個初級其余互斥同步基元,它在期待獲得鎖時停止扭轉。在多核盤算機上,當期待時光估計較短且少少湧現爭用情形時,SpinLock的機能將高於其他類型的鎖。不外,我們建議您僅在經由過程剖析肯定System.Threading.Monitor辦法或Interlocked辦法明顯下降了法式的機能時應用SpinLock。
即便SpinLock未獲得鎖,它也會發生線程的時光片。它如許做是為了不線程優先級別反轉,並使渣滓收受接管器可以或許持續履行。在應用SpinLock時,請確保任何線程持有鎖的時光不會跨越一個異常短的時光段,並確保任何線程在持有鎖時不會壅塞。
因為SpinLock是一個值類型,是以,假如您願望兩個正本都援用統一個鎖,則必需經由過程援用顯式傳遞該鎖。



usingSystem;
usingSystem.Text;
usingSystem.Threading;
usingSystem.Threading.Tasks;

namespaceMutiThreadSample.ThreadSynchronization
{
classSpinLockSample
{
publicstaticvoidTest()
{
SpinLocksLock=newSpinLock();
StringBuildersb=newStringBuilder();
Actionaction=()=>
{
boolgotLock=false;
for(inti=0;i<100;i++)
{
gotLock=false;
try
{
sLock.Enter(refgotLock);
sb.Append(i.ToString());
}
finally
{
//真正獲得以後,才釋放
if(gotLock)sLock.Exit();
}
}
};

//多線程挪用action
Parallel.Invoke(action,action,action);
Console.WriteLine("輸入:{0}",sb.ToString());
}
}
}

10、SpinWait(4.0後)

System.Threading.SpinWait是一個輕量同步類型,可以在初級別計劃中應用它來防止內核事宜所需的高開支的高低文切換和內核轉換。在多核盤算機上,當估計資本不會保存很長一段時光時,假如讓期待線程以用戶形式扭轉數十或數百個周期,然後從新測驗考試獲得資本,則效力會更高。假如在扭轉後資本變成可用的,則可以節儉數千個周期。假如資本依然弗成用,則只消費了大批周期,而且依然可以停止基於內核的期待。這一扭轉-期待的組合有時稱為“兩階段期待操作”。

上面的根本示例采取微軟案例:無鎖客棧


usingSystem;
usingSystem.Threading;

namespaceMutiThreadSample.ThreadSynchronization
{
publicclassLockFreeStack<T>
{
privatevolatileNodem_head;

privateclassNode{publicNodeNext;publicTValue;}

publicvoidPush(Titem)
{
varspin=newSpinWait();
Nodenode=newNode{Value=item},head;
while(true)
{
head=m_head;
node.Next=head;
if(Interlocked.CompareExchange(refm_head,node,head)==head)break;
spin.SpinOnce();
}
}

publicboolTryPop(outTresult)
{
result=default(T);
varspin=newSpinWait();

Nodehead;
while(true)
{
head=m_head;
if(head==null)returnfalse;
if(Interlocked.CompareExchange(refm_head,head.Next,head)==head)
{
result=head.Value;
returntrue;
}
spin.SpinOnce();
}
}
}
}

總結:

雖然有這麼多的技巧,然則分歧的技巧對應分歧的場景,我們必需熟習其特色和實用規模。在運用時,必需詳細成績詳細剖析,選擇最好的同步方法。

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