欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時間:2014年02月11日 10:39:47   作者:  
這篇文章主要介紹了c#線程同步使用方法,介紹幾種常用的C#進行線程同步的方式,需要的朋友可以參考下

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

線程同步的方式

線程同步有:臨界區(qū)、互斥區(qū)、事件、信號量四種方式
臨界區(qū)(Critical Section)、互斥量(Mutex)、信號量(Semaphore)、事件(Event)的區(qū)別
1、臨界區(qū):通過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數(shù)據(jù)訪問。在任意時刻只允許一個線程對共享資源進行訪問,如果有多個線程試圖訪問公共資源,那么在有一個線程進入后,其他試圖訪問公共資源的線程將被掛起,并一直等到進入臨界區(qū)的線程離開,臨界區(qū)在被釋放后,其他線程才可以搶占。
2、互斥量:采用互斥對象機制。 只有擁有互斥對象的線程才有訪問公共資源的權(quán)限,因為互斥對象只有一個,所以能保證公共資源不會同時被多個線程訪問?;コ獠粌H能實現(xiàn)同一應(yīng)用程序的公共資源安全共享,還能實現(xiàn)不同應(yīng)用程序的公共資源安全共享
3、信號量:它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數(shù)目
4、事 件: 通過通知操作的方式來保持線程的同步,還可以方便實現(xiàn)對多個線程的優(yōu)先級比較的操作

C#中常見線程同步方法

我們介紹幾種常用的C#進行線程同步的方式,這些方式可以根據(jù)其原理,找到對應(yīng)上面的四種類型之一。

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

根據(jù)經(jīng)驗,那些需要在多線程情況下被保護的資源通常是整型值,且這些整型值在多線程下最常見的操作就是遞增、遞減或相加操作。Interlocked類提供了一個專門的機制用于完成這些特定的操作。這個類提供了Increment、Decrement、Add靜態(tài)方法用于對int或long型變量的遞增、遞減或相加操作。此類的方法可以防止可能在下列情況發(fā)生的錯誤:計劃程序在某個線程正在更新可由其他線程訪問的變量時切換上下文;或者當兩個線程在不同的處理器上并發(fā)執(zhí)行時。 此類的成員不引發(fā)異常。

Increment和Decrement方法遞增或遞減變量并將結(jié)果值存儲在單個操作中。 在大多數(shù)計算機上,增加變量操作不是一個原子操作,需要執(zhí)行下列步驟:

1)將實例變量中的值加載到寄存器中。
2)增加或減少該值。
3)在實例變量中存儲該值。
如果不使用 Increment 和 Decrement,線程會在執(zhí)行完前兩個步驟后被搶先。 然后由另一個線程執(zhí)行所有三個步驟。 當?shù)谝粋€線程重新開始執(zhí)行時,它覆蓋實例變量中的值,造成第二個線程執(zhí)行增減操作的結(jié)果丟失。

Exchange 方法自動交換指定變量的值。 CompareExchange 方法組合了兩個操作:比較兩個值以及根據(jù)比較的結(jié)果將第三個值存儲在其中一個變量中。 比較和交換操作按原子操作執(zhí)行。

案例分析:共享打印機。

通常我們會使用共享打印機,幾臺計算機共享一臺打印機,每臺計算機可以發(fā)出打印指令,可能會出現(xiàn)并發(fā)情況。當然我們知道,打印機采用了隊列技術(shù)。為了簡化操作,我們假定,在打印機收到命令時,即可打印,而且在同一時間只能有一個打印任務(wù)在執(zhí)行。我們使用Interlocked方法來實現(xiàn)多線程同步。具體代碼如下:

復(fù)制代碼 代碼如下:

using System;
using System.Threading;

namespace MutiThreadSample.ThreadSynchronization
{
    class PrinterWithInterlockTest
    {
   /// <summary>
   /// 正在使用的打印機
   /// 0代表未使用,1代表正在使用
   /// </summary>
   public static int UsingPrinter = 0;
   /// <summary>
   /// 計算機數(shù)量
   /// </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>
   /// 線程執(zhí)行操作
   /// </summary>
   private static void MyThreadProc()
   {
  //使用打印機進行打印
  UsePrinter();
  //當前線程等待1秒
  Thread.Sleep(1000);
   }
   /// <summary>
   /// 使用打印機進行打印
   /// </summary>
   private static bool UsePrinter()
   {
  //檢查大引進是否在使用,如果原始值為0,則為未使用,可以進行打印,否則不能打印,繼續(xù)等待
  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 關(guān)鍵字

lock 關(guān)鍵字將語句塊標記為臨界區(qū),方法是獲取給定對象的互斥鎖,執(zhí)行語句,然后釋放該鎖。
lock 確保當一個線程位于代碼的臨界區(qū)時,另一個線程不進入臨界區(qū)。如果其他線程試圖進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放。

復(fù)制代碼 代碼如下:

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

lock 調(diào)用塊開始位置的 Enter 和塊結(jié)束位置的 Exit。

提供給 lock 關(guān)鍵字的參數(shù)必須為基于引用類型的對象,該對象用來定義鎖的范圍。在上例中,鎖的范圍限定為此函數(shù),因為函數(shù)外不存在任何對該對象的引用。嚴格地說,提供給 lock 的對象只是用來唯一地標識由多個線程共享的資源,所以它可以是任意類實例。然而,實際上,此對象通常表示需要進行線程同步的資源。例如,如果一個容器對象將被多個線程使用,則可以將該容器傳遞給 lock,而 lock 后面的同步代碼塊將訪問該容器。只要其他線程在訪問該容器前先鎖定該容器,則對該對象的訪問將是安全同步的。通常,最好避免鎖定 public 類型或鎖定不受應(yīng)用程序控制的對象實例,例如,如果該實例可以被公開訪問,則 lock(this) 可能會有問題,因為不受控制的代碼也可能會鎖定該對象。這可能導致死鎖,即兩個或更多個線程等待釋放同一對象。出于同樣的原因,鎖定公共數(shù)據(jù)類型(相比于對象)也可能導致問題。鎖定字符串尤其危險,因為字符串被公共語言運行庫 (CLR)“暫留”。這意味著整個程序中任何給定字符串都只有一個實例,就是這同一個對象表示了所有運行的應(yīng)用程序域的所有線程中的該文本。因此,只要在應(yīng)用程序進程中的任何位置處具有相同內(nèi)容的字符串上放置了鎖,就將鎖定應(yīng)用程序中該字符串的所有實例。因此,最好鎖定不會被暫留的私有或受保護成員。某些類提供專門用于鎖定的成員。例如,Array 類型提供 SyncRoot。許多集合類型也提供 SyncRoot。

常見的結(jié)構(gòu) lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 違反此準則:

1)如果實例可以被公共訪問,將出現(xiàn) lock (this) 問題。
2)如果 MyType 可以被公共訪問,將出現(xiàn) lock (typeof (MyType)) 問題。
3)由于進程中使用同一字符串的任何其他代碼將共享同一個鎖,所以出現(xiàn) lock(“myLock”) 問題。
最佳做法是定義 private 對象來鎖定, 或 private static 對象變量來保護所有實例所共有的數(shù)據(jù)。關(guān)于鎖的研究,大家可以參考:

案例分析:繼續(xù)使用共享打印機的案例

我們只需對前面的例子稍作修改即可實現(xiàn)lock進行同步。

聲明鎖對象:

復(fù)制代碼 代碼如下:

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

將打印方法修改如下:

復(fù)制代碼 代碼如下:

/// <summary>
/// 使用打印機進行打印
/// </summary>
private static void UsePrinter()
{
  //臨界區(qū)
  lock (UsingPrinterLocker)
  {
 Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
 //模擬打印操作
 Thread.Sleep(500);
 Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
  }
}

3、監(jiān)視器

與 lock 關(guān)鍵字類似,監(jiān)視器防止多個線程同時執(zhí)行代碼塊。Enter 方法允許一個且僅一個線程繼續(xù)執(zhí)行后面的語句;其他所有線程都將被阻止,直到執(zhí)行語句的線程調(diào)用 Exit。這與使用 lock 關(guān)鍵字一樣。事實上,lock 關(guān)鍵字就是用 Monitor 類來實現(xiàn)的。例如:(繼續(xù)修改共享打印機案例,增加方法UsePrinterWithMonitor)

復(fù)制代碼 代碼如下:

/// <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 關(guān)鍵字通常比直接使用 Monitor 類更可取,一方面是因為 lock 更簡潔,另一方面是因為 lock 確保了即使受保護的代碼引發(fā)異常,也可以釋放基礎(chǔ)監(jiān)視器。這是通過 finally 關(guān)鍵字來實現(xiàn)的,無論是否引發(fā)異常它都執(zhí)行關(guān)聯(lián)的代碼塊。

4、同步事件和等待句柄

使用鎖或監(jiān)視器對于防止同時執(zhí)行區(qū)分線程的代碼塊很有用,但是這些構(gòu)造不允許一個線程向另一個線程傳達事件。這需要“同步事件”,它是有兩個狀態(tài)(終止和非終止)的對象,可以用來激活和掛起線程。讓線程等待非終止的同步事件可以將線程掛起,將事件狀態(tài)更改為終止可以將線程激活。如果線程試圖等待已經(jīng)終止的事件,則線程將繼續(xù)執(zhí)行,而不會延遲。

同步事件有兩種:AutoResetEvent和ManualResetEvent。它們之間唯一的不同在于,無論何時,只要AutoResetEvent激活線程,它的狀態(tài)將自動從終止變?yōu)榉墙K止。相反,ManualResetEvent允許它的終止狀態(tài)激活任意多個線程,只有當它的Reset方法被調(diào)用時才還原到非終止狀態(tài)。

等待句柄,可以通過調(diào)用一種等待方法,如WaitOne、WaitAny或WaitAll,讓線程等待事件。System.Threading.WaitHandle.WaitOne使線程一直等待,直到單個事件變?yōu)榻K止狀態(tài);System.Threading.WaitHandle.WaitAny阻止線程,直到一個或多個指示的事件變?yōu)榻K止狀態(tài);System.Threading.WaitHandle.WaitAll阻止線程,直到所有指示的事件都變?yōu)榻K止狀態(tài)。當調(diào)用事件的Set方法時,事件將變?yōu)榻K止狀態(tài)。

AutoResetEvent允許線程通過發(fā)信號互相通信。通常,當線程需要獨占訪問資源時使用該類。線程通過調(diào)用AutoResetEvent上的WaitOne來等待信號。如果AutoResetEvent為非終止狀態(tài),則線程會被阻止,并等待當前控制資源的線程通過調(diào)用Set來通知資源可用。調(diào)用Set向AutoResetEvent發(fā)信號以釋放等待線程。AutoResetEvent將保持終止狀態(tài),直到一個正在等待的線程被釋放,然后自動返回非終止狀態(tài)。如果沒有任何線程在等待,則狀態(tài)將無限期地保持為終止狀態(tài)。如果當AutoResetEvent為終止狀態(tài)時線程調(diào)用WaitOne,則線程不會被阻止。AutoResetEvent將立即釋放線程并返回到非終止狀態(tài)。
可以通過將一個布爾值傳遞給構(gòu)造函數(shù)來控制AutoResetEvent的初始狀態(tài):如果初始狀態(tài)為終止狀態(tài),則為true;否則為false。
AutoResetEvent也可以同staticWaitAll和WaitAny方法一起使用。
案例:

案例介紹:

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

熬粥和做魚,是比較復(fù)雜的工作流程,
做粥:選材、淘米、熬制
做魚:洗魚、切魚、腌制、烹調(diào)
為了提高效率,我們用兩個線程來準備這頓飯,但是,現(xiàn)在只有一口鍋,只能等一個做完之后,另一個才能進行最后的烹調(diào)。

來看實例代碼:

復(fù)制代碼 代碼如下:

usingSystem;
usingSystem.Threading;

namespaceMutiThreadSample.ThreadSynchronization
{
///<summary>
///案例:做飯
///今天的Dinner準備吃魚,還要熬粥
///熬粥和做魚,是比較復(fù)雜的工作流程,
///做粥:選材、淘米、熬制
///做魚:洗魚、切魚、腌制、烹調(diào)
///我們用兩個線程來準備這頓飯
///但是,現(xiàn)在只有一口鍋,只能等一個做完之后,另一個才能進行最后的烹調(diào)
///</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},粥已經(jīng)做好,鍋閑了",Thread.CurrentThread.Name);

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

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

//等待鍋空閑出來
resetEvent.WaitOne();

//烹調(diào)
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與監(jiān)視器類似;它防止多個線程在某一時間同時執(zhí)行某個代碼塊。事實上,名稱“mutex”是術(shù)語“互相排斥(mutuallyexclusive)”的簡寫形式。然而與監(jiān)視器不同的是,mutex可以用來使跨進程的線程同步。mutex由Mutex類表示。當用于進程間同步時,mutex稱為“命名mutex”,因為它將用于另一個應(yīng)用程序,因此它不能通過全局變量或靜態(tài)變量共享。必須給它指定一個名稱,才能使兩個應(yīng)用程序訪問同一個mutex對象。
盡管mutex可以用于進程內(nèi)的線程同步,但是使用Monitor通常更為可取,因為監(jiān)視器是專門為.NETFramework而設(shè)計的,因而它可以更好地利用資源。相比之下,Mutex類是Win32構(gòu)造的包裝。盡管mutex比監(jiān)視器更為強大,但是相對于Monitor類,它所需要的互操作轉(zhuǎn)換更消耗計算資源。

本地mutex和系統(tǒng)mutex
Mutex分兩種類型:本地mutex和命名系統(tǒng)mutex。如果使用接受名稱的構(gòu)造函數(shù)創(chuàng)建了Mutex對象,那么該對象將與具有該名稱的操作系統(tǒng)對象相關(guān)聯(lián)。命名的系統(tǒng)mutex在整個操作系統(tǒng)中都可見,并且可用于同步進程活動。您可以創(chuàng)建多個Mutex對象來表示同一命名系統(tǒng)mutex,而且您可以使用OpenExisting方法打開現(xiàn)有的命名系統(tǒng)mutex。
本地mutex僅存在于進程當中。進程中引用本地Mutex對象的任意線程都可以使用本地mutex。每個Mutex對象都是一個單獨的本地mutex。

在本地Mutex中,用法與Monitor基本一致

繼續(xù)修改前面的打印機案例:

聲明Mutex對象:

復(fù)制代碼 代碼如下:

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

具體操作:

復(fù)制代碼 代碼如下:

///<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();
}
}

多線程調(diào)用:

復(fù)制代碼 代碼如下:

///<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>
///線程執(zhí)行操作
///</summary>
privatestaticvoidMyThreadProc()
{
//使用打印機進行打印
//UsePrinter();
//monitor同步
//UsePrinterWithMonitor();
//用Mutex同步
UsePrinterWithMutex();
//當前線程等待1秒
Thread.Sleep(1000);
}

最后的打印機案例代碼:

復(fù)制代碼 代碼如下:

usingSystem;
usingSystem.Threading;

namespaceMutiThreadSample.ThreadSynchronization
{
classPrinterWithLockTest
{
///<summary>
///正在使用的打印機
///</summary>
privatestaticobjectUsingPrinterLocker=newobject();
///<summary>
///計算機數(shù)量
///</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>
///線程執(zhí)行操作
///</summary>
privatestaticvoidMyThreadProc()
{
//使用打印機進行打印
//UsePrinter();
//monitor同步
//UsePrinterWithMonitor();
//用Mutex同步
UsePrinterWithMutex();
//當前線程等待1秒
Thread.Sleep(1000);
}
///<summary>
///使用打印機進行打印
///</summary>
privatestaticvoidUsePrinter()
{
//臨界區(qū)
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類允許多個線程同時讀取一個資源,但在向該資源寫入時要求線程等待以獲得獨占鎖。

可以在應(yīng)用程序中使用ReaderWriterLockSlim,以便在訪問一個共享資源的線程之間提供協(xié)調(diào)同步。獲得的鎖是針對ReaderWriterLockSlim本身的。
設(shè)計您應(yīng)用程序的結(jié)構(gòu),讓讀取和寫入操作的時間盡可能最短。因為寫入鎖是排他的,所以長時間的寫入操作會直接影響吞吐量。長時間的讀取操作會阻止處于等待狀態(tài)的編寫器,并且,如果至少有一個線程在等待寫入訪問,則請求讀取訪問的線程也將被阻止。

案例:構(gòu)造一個線程安全的緩存

復(fù)制代碼 代碼如下:

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類表示一個命名(系統(tǒng)范圍)信號量或本地信號量。它是一個對Win32信號量對象的精簡包裝。Win32信號量是計數(shù)信號量,可用于控制對資源池的訪問。
SemaphoreSlim類表示一個輕量的快速信號量,可用于在一個預(yù)計等待時間會非常短的進程內(nèi)進行等待。SemaphoreSlim會盡可能多地依賴由公共語言運行時(CLR)提供的同步基元。但是,它也會根據(jù)需要提供延遲初始化的、基于內(nèi)核的等待句柄,以支持等待多個信號量。SemaphoreSlim還支持使用取消標記,但它不支持命名信號量或使用等待句柄來進行同步。

線程通過調(diào)用WaitOne方法來進入信號量,此方法是從WaitHandle類派生的。當調(diào)用返回時,信號量的計數(shù)將減少。當一個線程請求項而計數(shù)為零時,該線程會被阻止。當線程通過調(diào)用Release方法釋放信號量時,將允許被阻止的線程進入。并不保證被阻塞的線程進入信號量的順序,例如先進先出(FIFO)或后進先出(LIFO)。信號量的計數(shù)在每次線程進入信號量時減小,在線程釋放信號量時增加。當計數(shù)為零時,后面的請求將被阻塞,直到有其他線程釋放信號量。當所有的線程都已釋放信號量時,計數(shù)達到創(chuàng)建信號量時所指定的最大值。

案例分析:購買火車票

還得排隊進行購買,購買窗口是有限的,只有窗口空閑時才能購買

復(fù)制代碼 代碼如下:

usingSystem;
usingSystem.Threading;

namespaceMutiThreadSample.ThreadSynchronization
{
///<summary>
///案例:支付流程
///如超市、藥店、火車票等,都有限定的幾個窗口進行結(jié)算,只有有窗口空閑,才能進行結(jié)算。
///我們就用多線程來模擬結(jié)算過程
///</summary>
classPaymentWithSemaphore
{
///<summary>
///聲明收銀員總數(shù)為3個,但是當前空閑的個數(shù)為0,可能還沒開始上班。
///</summary>
privatestaticSemaphoreIdleCashiers=newSemaphore(0,3);
///<summary>
///測試支付過程
///</summary>
publicstaticvoidTestPay()
{
ParameterizedThreadStartstart=newParameterizedThreadStart(Pay);
//假設(shè)同時有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);
//結(jié)算
Thread.Sleep(2000);
Console.WriteLine("Thread{0}:Thepaymenthasbeenfinished.",obj);

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

8、障礙(Barrier)4.0后技術(shù)

使多個任務(wù)能夠采用并行方式依據(jù)某種算法在多個階段中協(xié)同工作。
通過在一系列階段間移動來協(xié)作完成一組任務(wù),此時該組中的每個任務(wù)發(fā)信號指出它已經(jīng)到達指定階段的Barrier并且暗中等待其他任務(wù)到達。相同的Barrier可用于多個階段。

9、SpinLock(4.0后)
SpinLock結(jié)構(gòu)是一個低級別的互斥同步基元,它在等待獲取鎖時進行旋轉(zhuǎn)。在多核計算機上,當?shù)却龝r間預(yù)計較短且極少出現(xiàn)爭用情況時,SpinLock的性能將高于其他類型的鎖。不過,我們建議您僅在通過分析確定System.Threading.Monitor方法或Interlocked方法顯著降低了程序的性能時使用SpinLock。
即使SpinLock未獲取鎖,它也會產(chǎn)生線程的時間片。它這樣做是為了避免線程優(yōu)先級別反轉(zhuǎn),并使垃圾回收器能夠繼續(xù)執(zhí)行。在使用SpinLock時,請確保任何線程持有鎖的時間不會超過一個非常短的時間段,并確保任何線程在持有鎖時不會阻塞。
由于SpinLock是一個值類型,因此,如果您希望兩個副本都引用同一個鎖,則必須通過引用顯式傳遞該鎖。


復(fù)制代碼 代碼如下:

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();
}
}
};

//多線程調(diào)用action
Parallel.Invoke(action,action,action);
Console.WriteLine("輸出:{0}",sb.ToString());
}
}
}

10、SpinWait(4.0后)

System.Threading.SpinWait是一個輕量同步類型,可以在低級別方案中使用它來避免內(nèi)核事件所需的高開銷的上下文切換和內(nèi)核轉(zhuǎn)換。在多核計算機上,當預(yù)計資源不會保留很長一段時間時,如果讓等待線程以用戶模式旋轉(zhuǎn)數(shù)十或數(shù)百個周期,然后重新嘗試獲取資源,則效率會更高。如果在旋轉(zhuǎn)后資源變?yōu)榭捎玫?,則可以節(jié)省數(shù)千個周期。如果資源仍然不可用,則只花費了少量周期,并且仍然可以進行基于內(nèi)核的等待。這一旋轉(zhuǎn)-等待的組合有時稱為“兩階段等待操作”。

下面的基本示例采用微軟案例:無鎖堆棧

復(fù)制代碼 代碼如下:

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();
}
}
}
}

總結(jié):

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

相關(guān)文章

最新評論