亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C# > 正文

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

2020-01-24 02:48:41
字體:
來源:轉載
供稿:網友

在應用程序中使用多個線程的一個好處是每個線程都可以異步執行。對于 Windows 應用程序,耗時的任務可以在后臺執行,而使應用程序窗口和控件保持響應。對于服務器應用程序,多線程處理提供了用不同線程處理每個傳入請求的能力。否則,在完全滿足前一個請求之前,將無法處理每個新請求。然而,線程的異步特性意味著必須協調對資源(如文件句柄、網絡連接和內存)的訪問。否則,兩個或更多的線程可能在同一時間訪問相同的資源,而每個線程都不知道其他線程的操作。

線程同步的方式

線程同步有:臨界區、互斥區、事件、信號量四種方式
臨界區(Critical Section)、互斥量(Mutex)、信號量(Semaphore)、事件(Event)的區別
1、臨界區:通過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問。在任意時刻只允許一個線程對共享資源進行訪問,如果有多個線程試圖訪問公共資源,那么在有一個線程進入后,其他試圖訪問公共資源的線程將被掛起,并一直等到進入臨界區的線程離開,臨界區在被釋放后,其他線程才可以搶占。
2、互斥量:采用互斥對象機制。 只有擁有互斥對象的線程才有訪問公共資源的權限,因為互斥對象只有一個,所以能保證公共資源不會同時被多個線程訪問?;コ獠粌H能實現同一應用程序的公共資源安全共享,還能實現不同應用程序的公共資源安全共享
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();
}
}
}
}

總結:

盡管有這么多的技術,但是不同的技術對應不同的場景,我們必須熟悉其特點和適用范圍。在應用時,必須具體問題具體分析,選擇最佳的同步方式。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美中文字幕视频在线观看| 日韩最新中文字幕电影免费看| 国产精品免费一区豆花| 伊人久久精品视频| 不卡av电影在线观看| 欧美精品一区三区| 国产做受69高潮| 亚洲综合在线播放| 日韩精品免费看| 亚洲人成毛片在线播放| 91在线中文字幕| 91产国在线观看动作片喷水| 国产精品v日韩精品| 亚洲综合在线播放| 欧美成人精品不卡视频在线观看| 色老头一区二区三区在线观看| 国产精品免费视频久久久| 97视频在线观看免费高清完整版在线观看| 国产精品美女主播在线观看纯欲| 欧美在线视频观看免费网站| 国内自拍欧美激情| 亚洲欧美国产精品| 91热福利电影| 久久国产精品影片| 久久精品成人欧美大片| 欧美激情在线观看视频| 在线看福利67194| 国产成人综合一区二区三区| 欧美精品午夜视频| 国产啪精品视频| 国产精品久久电影观看| 日韩欧美在线国产| 国产精品永久免费视频| 成人网中文字幕| 影音先锋欧美在线资源| 欧美电影在线观看高清| 亚洲无av在线中文字幕| 欧美日韩国产成人在线观看| 亚洲一区二区三区在线视频| 中文字幕精品久久| 欧美亚洲在线播放| 91久久久久久久一区二区| 欧美一级视频免费在线观看| 韩国v欧美v日本v亚洲| 久久色免费在线视频| 亚洲午夜未删减在线观看| 久久精品视频一| 中文字幕日韩av电影| 国产在线观看91精品一区| 欧美日韩一区二区在线播放| 性欧美亚洲xxxx乳在线观看| 久久国产天堂福利天堂| 91国产精品电影| 欧美国产激情18| 中文字幕日韩免费视频| 久久综合久久美利坚合众国| 国产亚洲一区二区在线| 国产成人av在线| 欧美国产精品人人做人人爱| 国产亚洲成av人片在线观看桃| 国产精品流白浆视频| 国产精品日韩专区| 欧美国产日韩一区二区在线观看| 中文字幕无线精品亚洲乱码一区| 久久99久久99精品免观看粉嫩| 欧美极度另类性三渗透| 日本高清不卡的在线| 亚洲精品久久久久久久久久久久久| 国产精品∨欧美精品v日韩精品| 久久亚洲春色中文字幕| 欧美成人午夜剧场免费观看| 欧美日韩aaaa| 人人做人人澡人人爽欧美| 亚洲午夜国产成人av电影男同| 亚洲欧美另类人妖| 超碰91人人草人人干| 亚洲精品第一国产综合精品| 伊人伊成久久人综合网小说| 欧美激情18p| 久久久成人精品视频| 色综合五月天导航| www.xxxx欧美| 亚洲最大的av网站| 亚洲va码欧洲m码| 亚洲国产欧美久久| 国产日韩欧美日韩| 久久国产天堂福利天堂| 国产97在线|亚洲| 国产91在线播放| 欧美成人午夜激情在线| 国产91网红主播在线观看| 国产欧美日韩亚洲精品| 亚洲国产一区二区三区四区| 欧美性猛交xxxxx免费看| 国产日韩欧美91| xxxxx成人.com| 色噜噜狠狠狠综合曰曰曰| 精品福利在线看| 日韩欧美在线播放| 国产精品高潮呻吟久久av黑人| 久久久久久久久爱| 国产精品444| 日韩免费视频在线观看| 国产精品丝袜久久久久久高清| 午夜精品在线视频| 国产精品一区=区| 久久色免费在线视频| 国产精品丝袜一区二区三区| 国产精品爽爽ⅴa在线观看| 日韩av成人在线观看| 国产日本欧美视频| 欧美午夜美女看片| 91亚洲人电影| 欧美日韩中文在线观看| 国内精品美女av在线播放| 国产精品久久久久久av| 国产一区二区三区视频免费| 国产成人啪精品视频免费网| 国产精品热视频| 亚洲影院在线看| 日韩国产欧美精品一区二区三区| 国产精品偷伦视频免费观看国产| 福利视频第一区| 中文字幕亚洲第一| 中文字幕欧美日韩| 亚洲精品按摩视频| 国产精品自拍网| 日韩最新av在线| 久久精品国亚洲| 欧美午夜影院在线视频| 亚洲精品国产拍免费91在线| 欧美老妇交乱视频| 欧美激情精品久久久久久免费印度| 青青草原成人在线视频| 欧美成人免费观看| 亚洲精品久久久久久久久久久| 在线观看欧美日韩国产| 国产精品黄色影片导航在线观看| 欧美性受xxxx白人性爽| 国内外成人免费激情在线视频| 亚洲一区二区三| 国产在线久久久| 国产精品视频最多的网站| 欧美美女操人视频| 一区二区欧美亚洲| 在线观看国产成人av片| 综合欧美国产视频二区| 精品在线小视频| 另类天堂视频在线观看| 亚洲一区二区三区sesese| 欧美日韩在线免费| 国产+人+亚洲| 亚洲第一中文字幕在线观看| 91在线高清免费观看| 国产精品一区二区性色av| 久久久久久国产免费| 国产精品精品视频| 高清欧美性猛交| 日韩av123| 一区二区福利视频| 国产精品久久一区| 欧美在线一级视频| 国内精品小视频在线观看| 日本久久久久久久久久久|