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

C#實現(xiàn)多線程的同步方法實例分析

 更新時間:2015年04月03日 09:28:51   作者:xugang  
這篇文章主要介紹了C#實現(xiàn)多線程的同步方法,實例分析了C#線程同步的實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下

本文主要描述在C#中線程同步的方法。線程的基本概念網(wǎng)上資料也很多就不再贅述了。直接接入 主題,在多線程開發(fā)的應(yīng)用中,線程同步是不可避免的。在.Net框架中,實現(xiàn)線程同步主要通過以下的幾種方式來實現(xiàn),在MSDN的線程指南中已經(jīng)講了幾種,這里結(jié)合作者實際中用到的方式一起說明一下。

1. 維護自由鎖(InterLocked)實現(xiàn)同步

2. 監(jiān)視器(Monitor)和互斥鎖(lock)

3. 讀寫鎖(ReadWriteLock)

4. 系統(tǒng)內(nèi)核對象

1) 互斥(Mutex), 信號量(Semaphore), 事件(AutoResetEvent/ManualResetEvent)

2) 線程池

除了以上的這些對象之外實現(xiàn)線程同步的還可以使用Thread.Join方法。這種方法比較簡單,當(dāng)你在第一個線程運行時想等待第二個線程執(zhí)行結(jié)果,那么你可以讓第二個線程Join進來就可以了。

自由鎖(InterLocked)

對一個32位的整型數(shù)進行遞增和遞減操作來實現(xiàn)鎖,有人會問為什么不用++或--來 操作。因為在多線程中對鎖進行操作必須是原子的,而++和--不具備這個能力。InterLocked類還提供了兩個另外的函數(shù)Exchange, CompareExchange用于實現(xiàn)交換和比較交換。Exchange操作會將新值設(shè)置到變量中并返回變量的原來值: int oVal = InterLocked.Exchange(ref val, 1)。

監(jiān)視器(Monitor)

在MSDN中對Monitor的描述是: Monitor 類通過向單個線程授予對象鎖來控制對對象的訪問。

Monitor類是一個靜態(tài)類因此你不能通過實例化來得到類的對象。Monitor 的成員可以查看MSDN,基本上Monitor的效果和lock是一樣的,通過加鎖操作Enter設(shè)置臨界區(qū),完成操作后使用Exit操作來釋放對象鎖。 不過相對來說Monitor的功能更強,Moniter可以進行測試鎖的狀態(tài),因此你可以控制對臨界區(qū)的訪問選擇,等待or離開, 而且Monitor還可以在釋放鎖之前通知指定的對象,更重要的是使用Monitor可以跨越方法來操作。Monitor提供的方法很少就只有獲取鎖的方 法Enter, TryEnter;釋放鎖的方法Wait, Exit;還有消息通知方法Pulse, PulseAll。經(jīng)典的Monitor操作是這樣的:

// 通監(jiān)視器來創(chuàng)建臨界區(qū) 
static public void DelUser(string name)
{
  try
  {
  // 等待線程進入 
  Monitor.Enter(Names);
  Names.Remove(name);
  Console.WriteLine("Del: {0}", Names.Count);
  Monitor.Pulse(Names);
  }
  finally
  {
  // 釋放對象鎖 
  Monitor.Exit(Names);
  }
 } 
}

其中Names是一個List, 這里有一個小技巧,如果你想聲明整個方法為線程同步可以使用方法屬性:

// 通過屬性設(shè)置整個方法為臨界區(qū) 
[MethodImpl(MethodImplOptions.Synchronized)] 
static public void AddUser(string name) 
{ 
 Names.Add(name); 
 Console.WriteLine("Add: {0}",Names.Count); 
}

對于Monitor的使用有一個方法是比較詭異的,那就是Wait方法。在MSDN中對Wait的描述是: 釋放對象上的鎖以便允許其他線程鎖定和訪問該對象。

這里提到的是先釋放鎖,那么顯然我們需要先得到鎖,否則調(diào)用Wait會出現(xiàn)異常,所 以我們必須在Wait前面調(diào)用Enter方法或其他獲取鎖的方法,如lock,這點很重要。對應(yīng)Enter方法,Monitor給出來另一種實現(xiàn) TryEnter。這兩種方法的主要區(qū)別在于是否阻塞當(dāng)前線程,Enter方法在獲取不到鎖時,會阻塞當(dāng)前線程直到得到鎖。不過缺點是如果永遠(yuǎn)得不到鎖那 么程序就會進入死鎖狀態(tài)。我們可以采用Wait來解決,在調(diào)用Wait時加入超時時限就可以。

if (Monitor.TryEnter(Names))
{
 Monitor.Wait(Names, 1000); // !! 
 Names.Remove(name); 
 Console.WriteLine("Del: {0}", Names.Count);
 Monitor.Pulse(Names); 
}

互斥鎖(lock)

lock關(guān)鍵字是實現(xiàn)線程同步的比較簡單的方式,其實就是設(shè)置一個臨界區(qū)。在 lock之后的{...}區(qū)塊為一個臨界區(qū),當(dāng)進入臨界區(qū)時加互斥鎖,離開臨界區(qū)時釋放互斥鎖。MSDN對lock關(guān)鍵字的描述是: lock 關(guān)鍵字可將語句塊標(biāo)記為臨界區(qū),方法是獲取給定對象的互斥鎖,執(zhí)行語句,然后釋放該鎖。

具體例子如下:

static public void ThreadFunc(object name)
{
 string str = name as string;
 Random rand = new Random();
 int count = rand.Next(100, 200);
 for (int i = 0; i < count; i++)
 {
 lock (NumList)
 {
 NumList.Add(i);
 Console.WriteLine("{0} {1}", str, i);
 }
 }
}

對lock的使用有幾點建議:對實例鎖定lock(this),對靜態(tài)變量鎖定lock(typeof(val))。lock的對象訪問權(quán)限最好是private,否則會出現(xiàn)失去訪問控制現(xiàn)象。

讀寫鎖(ReadWriteLock)

讀寫鎖的出現(xiàn)主要是在很多情況下,我們讀資源的操作要多于寫資源的操作。但是如果每 次只對資源賦予一個線程的訪問權(quán)限顯然是低效的,讀寫鎖的優(yōu)勢是同時可以有多個線程對同一資源進行讀操作。因此在讀操作比寫操作多很多,并且寫操作的時間 很短的情況下使用讀寫鎖是比較有效率的。讀寫鎖是一個非靜態(tài)類所以你在使用前需要先聲明一個讀寫鎖對象:

static private ReaderWriterLock _rwlock = new ReaderWriterLock();

讀寫鎖是通過調(diào)用AcquireReaderLock,ReleaseReaderLock,AcquireWriterLock,ReleaseWriterLock來完成讀鎖和寫鎖控制的

static public void ReaderThread(int thrdId) 
{ 
  try 
  { // 請求讀鎖,如果100ms超時退出 
  _rwlock.AcquireReaderLock(10); 
  try 
  { 
   int inx = _rand.Next(_list.Count); 
   if (inx < _list.Count) 
   Console.WriteLine("{0}thread {1}", thrdId, _list[inx]); 
  } 
  finally 
  {
   _rwlock.ReleaseReaderLock(); 
  } 
  } 
  catch (ApplicationException) // 如果請求讀鎖失敗 
  { 
  Console.WriteLine("{0}thread get reader lock out time!", thrdId); 
  } 
 } 
 static public void WriterThread() 
 { 
  try 
  {
  // 請求寫鎖 
  _rwlock.AcquireWriterLock(100); 
  try 
  { 
   string val = _rand.Next(200).ToString(); 
   _list.Add(val); // 寫入資源 
   Console.WriteLine("writer thread has written {0}", val); 
  } 
  finally 
  { // 釋放寫鎖 
   _rwlock.ReleaseWriterLock(); 
  } 
  } 
  catch (ApplicationException) 
  { 
  Console.WriteLine("Get writer thread lock out time!"); 
  } 
}

如果你想在讀的時候插入寫操作請使用UpgradeToWriterLock和DowngradeFromWriterLock來進行操作,而不是釋放讀鎖。

static private void UpgradeAndDowngrade(int thrdId) 
{ 
 try 
 { 
 _rwlock.AcquireReaderLock(10); 
 try 
 { 
  try 
  {
  // 提升讀鎖到寫鎖 
  LockCookie lc = _rwlock.UpgradeToWriterLock(100);
  try
  {
  string val = _rand.Next(500).ToString();
  _list.Add(val); Console.WriteLine
("Upgrade Thread{0} add {1}", thrdId, val); 
  } 
  finally
  { // 下降寫鎖 
  _rwlock.DowngradeFromWriterLock(ref lc); 
  } 
  } 
  catch (ApplicationException)
  { 
  Console.WriteLine("{0}thread upgrade reader lock failed!", thrdId); 
  } 
 } 
 finally 
 {
  // 釋放原來的讀鎖 
  _rwlock.ReleaseReaderLock();
 }
 } 
 catch (ApplicationException) 
 { 
 Console.WriteLine("{0}thread get reader lock out time!", thrdId);
 }
}

這里有一點要注意的就是讀鎖和寫鎖的超時等待時間間隔的設(shè)置。通常情況下設(shè)置寫鎖的等待超時要比讀鎖的長,否則會經(jīng)常發(fā)生寫鎖等待失敗的情況。

系統(tǒng)內(nèi)核對象 互斥對象(Mutex)

互斥對象的作用有點類似于監(jiān)視器對象,確保一個代碼塊在同一時刻只有一個線程在執(zhí) 行?;コ鈱ο蠛捅O(jiān)視器對象的主要區(qū)別就是,互斥對象一般用于跨進程間的線程同步,而監(jiān)視器對象則用于進程內(nèi)的線程同步?;コ鈱ο笥袃煞N:一種是命名互斥; 另一種是匿名互斥。在跨進程中使用到的就是命名互斥,一個已命名的互斥就是一個系統(tǒng)級的互斥,它可以被其他進程所使用,只要在創(chuàng)建互斥時指定打開互斥的名 稱就可以。在.Net中互斥是通過Mutex類來實現(xiàn)。

其實對于OpenExisting函數(shù)有兩個重載版本,

Mutex.OpenExisting (String)

Mutex.OpenExisting (String, MutexRights)

對于默認(rèn)的第一個函數(shù)其實是實現(xiàn)了第二個函數(shù) MutexRights.Synchronize|MutexRights.Modify操作。

由于監(jiān)視器的設(shè)計是基于.Net框架,而Mutex類是系統(tǒng)內(nèi)核對象封裝了win32的一個內(nèi)核結(jié)構(gòu)來實現(xiàn)互斥,并且互斥操作需要請求中斷來完成,因此在進行進程內(nèi)線程同步的時候性能上要比互斥要好。

典型的使用Mutex同步需要完成三個步驟的操作:1.打開或者創(chuàng)建一個Mutex實例;2.調(diào)用WaitOne()來請求互斥對象;3.最后調(diào)用ReleaseMutex來釋放互斥對象。

static public void AddString(string str) 
{
 // 設(shè)置超時時限并在wait前退出非默認(rèn)托管上下文 
 if (_mtx.WaitOne(1000, true))
 {
 _resource.Add(str); 
 _mtx.ReleaseMutex(); 
 } 
}

需要注意的是,WaitOne和ReleaseMutex必須成對出現(xiàn),否則會導(dǎo)致進程死鎖的發(fā)生,這時系統(tǒng)(.Net2.0)框架會拋出AbandonedMutexException異常。

信號量(Semaphore)

信號量就像一個夜總會:它有確切的容量,并被保鏢控制。一旦滿員,就沒有人能再進入,其他人必須在外面排隊。那么在里面離開一個人后,隊頭的人就可以進入。信號量的構(gòu)造函數(shù)需要提供至少兩個參數(shù)-現(xiàn)有的人數(shù)和最大的人數(shù)。

信號量的行為有點類似于Mutex或是lock,但是信號量沒有擁有者。任意線程都可以調(diào)用Release來釋放信號量而不像Mutex和lock那樣需要線程得到資源才能釋放。

class SemaphoreTest 
{
 static Semaphore s = new Semaphore(3, 3); // 當(dāng)前值=3; 容量=3 
 static void Main() 
 {
  for (int i = 0; i < 10; i++) 
  new Thread(Go).Start(); 
 } 
 static void Go()
 {
  while (true)
  {
  s.WaitOne();
  Thread.Sleep(100); // 一次只有個線程能被處理 
  s.Release(); 
  } 
 }
} 

事件(ManualResetEvent/AutoResetEvent) 
< src="http://blog.csdn.net/count.aspx?ID=1857459&Type=Rank" 
type="text/javascript">

AutoResetEvent

一個AutoResetEvent象是一個"檢票輪盤":插入一張通行證然后讓一個 人通過。"auto"的意思就是這個"輪盤"自動關(guān)閉或者打開讓某人通過。線程將在調(diào)用WaitOne后進行等待或者是阻塞,并且通過調(diào)用Set操作來插 入線程。如果一堆線程調(diào)用了WaitOne操作,那么"輪盤"就會建立一個等待隊列。一個通行證可以來自任意一個線程,換句話說任意一個線程都可以通過訪 問AutoResetEvent對象并調(diào)用Set來釋放一個阻塞的線程。

如果在Set被調(diào)用的時候沒有線程等待,那么句柄就會一直處于打開狀態(tài)直到有線程調(diào) 用了WaitOne操作。這種行為避免了競爭條件-當(dāng)一個線程還沒來得急釋放而另一個線程就開始進入的情況。因此重復(fù)的調(diào)用Set操作一個"輪盤"哪怕是 沒有等待線程也不會一次性的讓所有線程進入。

WaitOne操作接受一個超時參數(shù)-當(dāng)發(fā)生等待超時的時候,這個方法會返回一個 false。當(dāng)已有一個線程在等待的時候,WaitOne操作可以指定等待還是退出當(dāng)前同步上下文。Reset操作提供了關(guān)閉"輪盤"的操作。 AutoResetEvent能夠通過兩個方法來創(chuàng)建: 1.調(diào)用構(gòu)造函數(shù) EventWaitHandle wh = new AutoResetEvent (false); 如果boolean值為true,那么句柄的Set操作將在創(chuàng)建后自動被調(diào)用 ;2. 通過基類EventWaitHandle方式 EventWaitHandle wh = new EventWaitHandle (false, EventResetMode.Auto); EventWaitHandle構(gòu)造函數(shù)允許創(chuàng)建一個ManualResetEvent。人們應(yīng)該通過調(diào)用Close來釋放一個Wait Handle在它不再使用的時候。當(dāng)在應(yīng)用程序的生存期內(nèi)Wait handle繼續(xù)被使用,那么如果遺漏了Close這步,在應(yīng)用程序關(guān)閉的時候也會被自動釋放。

class BasicWaitHandle 
{
 static EventWaitHandle wh = new AutoResetEvent(false);
 static void Main()
 {
  new Thread(Waiter).Start();
  Thread.Sleep(1000); // 等待一會兒 
  wh.Set(); // 喚醒 
 } 
 static void Waiter()
 {
  Console.WriteLine("Waiting...");
  wh.WaitOne(); // 等待喚醒 
  Console.WriteLine("Notified"); 
 }
}

ManualResetEvent

ManualResetEvent是AutoResetEvent的一個特例。它的 不同之處在于在線程調(diào)用WaitOne后不會自動的重置狀態(tài)。它的工作機制有點象是開關(guān):調(diào)用Set打開并允許其他線程進行WaitOne;調(diào)用 Reset關(guān)閉那么排隊的線程就要等待,直到下一次打開。可以使用一個帶volatile聲明的boolean字段來模擬間斷休眠 - 通過重復(fù)檢測標(biāo)志,然后休眠一小段時間。

ManualResetEvent常常被用于協(xié)助完成一個特殊的操作,或者讓一個線程在開始工作前完成初始化。

線程池(Thread Pooling)

如果你的應(yīng)用程序擁有大量的線程并花費大量的時間阻塞在一個Wait Handle上,那么你要考慮使用線程池(Thead pooling)來處理。線程池通過合并多個Wait Handle來節(jié)約等待的時間。當(dāng)Wait Handle被激活時,使用線程池你需要注冊一個Wait Handle到一個委托去執(zhí)行。通過調(diào)用ThreadPool.RegisterWaitForSingleObject方法:

class Test 
{
 static ManualResetEvent starter = new ManualResetEvent(false); 
 public static void Main()
 {
  ThreadPool.RegisterWaitForSingleObject(starter,Go,"hello",-1,true);
  Thread.Sleep(5000);
  Console.WriteLine("Signaling worker...");
  starter.Set(); 
  Console.ReadLine();
 }
 public static void Go(object data, bool timedOut) 
 {
  Console.WriteLine("Started " + data); // Perform task... 
 }
}

對于Wait Handle和委托,RegisterWaitForSingleObject接受一個"黑盒"對象并傳遞給你的委托(就像 ParameterizedThreadStart),超時設(shè)置和boolean標(biāo)志指示了關(guān)閉和循環(huán)的請求。所有進入池中的線程都被認(rèn)為是后臺線程,這 就意味著它們不再由應(yīng)用程序控制,而是由系統(tǒng)控制直到應(yīng)用程序退出。

注意:如果這時候調(diào)用Abort操作,可能會發(fā)生意想不到的情況。

你也可以通過調(diào)用QueueUserWorkItem方法使用線程池,指定委托并立即被執(zhí)行。這時你不能在多任務(wù)情況下保存共享線程,但是可以得到另外的好處:線程池會保持一個線程的總?cè)萘浚?dāng)作業(yè)數(shù)超出容量時自動插入任務(wù)。

class Test 
{
 static object workerLocker = new object();
 static int runningWorkers = 100;
 public static void Main() 
 {
  for (int i = 0; i < runningWorkers; i++) 
  {
  ThreadPool.QueueUserWorkItem(Go, i); 
  }
  Console.WriteLine("Waiting for threads to complete..."); 
  lock (workerLocker) 
  {
  while (runningWorkers > 0) 
   Monitor.Wait(workerLocker);
  }
  Console.WriteLine("Complete!");
  Console.ReadLine(); 
 }
 public static void Go(object instance) 
 {
  Console.WriteLine("Started: " + instance);
  Thread.Sleep(1000); 
  Console.WriteLine("Ended: " + instance); 
  lock (workerLocker)
  {
  runningWorkers--;
  Monitor.Pulse(workerLocker);
  }
 }
}

為了傳遞多個對象到目標(biāo)方法,你必須定義一個客戶對象并包含所有屬性或通過調(diào)用異步的委托。如Go方法接受兩參數(shù):

ThreadPool.QueueUserWorkItem (delegate (object notUsed) { Go (23,34); });

其他的方法可以使用異步委托。

希望本文所述對大家的C#程序設(shè)計有所幫助。

相關(guān)文章

  • c#常用表格控件dataGridView的分頁顯示

    c#常用表格控件dataGridView的分頁顯示

    最近項目需要自己寫了一個用來給DataGridView分頁用的控件,下面這篇文章主要給大家介紹了關(guān)于c#常用表格控件dataGridView的分頁顯示,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-10-10
  • visio二次開發(fā)--判斷文檔是否已發(fā)生變化(變化就加星號*)

    visio二次開發(fā)--判斷文檔是否已發(fā)生變化(變化就加星號*)

    最近做一個故障樹診斷的項目,用visio二次開發(fā),可以同時打開多個繪制的故障樹圖形文檔。項目中需要實現(xiàn)判斷文檔是否發(fā)生變化,這是很多編輯軟件的基本功能,變化了就加個星號*
    2013-04-04
  • C#提高編程能力的50個要點總結(jié)

    C#提高編程能力的50個要點總結(jié)

    這篇文章主要介紹了C#提高編程能力的50個要點,較為詳細(xì)的總結(jié)分析了C#程序設(shè)計中常見的注意事項與編程技巧,需要的朋友可以參考下
    2016-02-02
  • 輕松學(xué)習(xí)C#的異常處理

    輕松學(xué)習(xí)C#的異常處理

    輕松學(xué)習(xí)C#的異常處理,對C#的異常處理感興趣的朋友可以參考本篇文章,幫助大家更靈活的運用C#的異常處理
    2015-11-11
  • c#抽簽系統(tǒng)的實現(xiàn)示例

    c#抽簽系統(tǒng)的實現(xiàn)示例

    本文主要介紹了c#抽簽系統(tǒng)的實現(xiàn)示例,一個基于c#的簡單抽簽系統(tǒng),可以重新導(dǎo)入數(shù)據(jù),清空數(shù)據(jù)。文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • C# DateTime日期比較方法案例詳解

    C# DateTime日期比較方法案例詳解

    這篇文章主要介紹了C# DateTime日期比較方法案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • 三十分鐘快速掌握C# 6.0知識點

    三十分鐘快速掌握C# 6.0知識點

    這篇文章主要介紹了C# 6.0的相關(guān)知識點,文中介紹的非常詳細(xì),通過這篇文字可以讓大家在三十分鐘內(nèi)快速的掌握C# 6.0,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-03-03
  • WPF+ASP.NET SignalR實現(xiàn)后臺通知功能的示例代碼

    WPF+ASP.NET SignalR實現(xiàn)后臺通知功能的示例代碼

    本文以一個簡單示例,簡述如何通過WPF+ASP.NET SignalR實現(xiàn)消息后臺通知以及數(shù)據(jù)的實時刷新,僅供學(xué)習(xí)分享使用,如有不足之處,還請指正
    2022-09-09
  • C#解決漢諾塔問題DEMO

    C#解決漢諾塔問題DEMO

    這篇文章主要介紹了C#解決漢諾塔問題DEMO,本文直接給出實現(xiàn)代碼,需要的朋友可以參考下
    2015-05-05
  • 深入講解C#編程中嵌套類型和匿名類型的定義與使用

    深入講解C#編程中嵌套類型和匿名類型的定義與使用

    這篇文章主要介紹了C#編程中嵌套類型和匿名類型的定義與使用,包括在SQL語句中使用匿名類型的方法,需要的朋友可以參考下
    2016-01-01

最新評論