C#多線程進修之(三)臨盆者和花費者用法剖析。本站提示廣大學習愛好者:(C#多線程進修之(三)臨盆者和花費者用法剖析)文章只能為提供參考,不一定能成為您想要的結果。以下是C#多線程進修之(三)臨盆者和花費者用法剖析正文
本文實例講述了C#多線程進修之臨盆者和花費者用法。分享給年夜家供年夜家參考。詳細實剖析以下:
後面的文章說過,每一個線程都有本身的資本,然則代碼區是同享的,即每一個線程都可以履行雷同的函數。這能夠帶來的成績就是幾個線程同時履行一個函數,招致數據的凌亂,發生弗成預感的成果,是以我們必需防止這類情形的產生。
C#供給了一個症結字lock,它可以把一段代碼界說為互斥段(critical section),互斥段在一個時辰內只許可一個線程進入履行,而其他線程必需期待。在C#中,症結字lock界說以下:
lock(expression) statement_block
expression代表你願望跟蹤的對象,平日是對象援用。
假如你想掩護一個類的實例,普通地,你可使用this;
假如你想掩護一個靜態變量(如互斥代碼段在一個靜態辦法外部),普通應用類名便可以了。
而statement_block就是互斥段的代碼,這段代碼在一個時辰內只能夠被一個線程履行。
上面是一個應用lock症結字的典范例子,在正文裡解釋了lock症結字的用法和用處。
示例以下:
using System; using System.Threading; namespace ThreadSimple { internal class Account { int balance; Random r = new Random(); internal Account(int initial) { balance = initial; } internal int Withdraw(int amount) { if (balance < 0) { //假如balance小於0則拋出異常 throw new Exception("Negative Balance"); } //上面的代碼包管在以後線程修正balance的值完成之前 //不會有其他線程也履行這段代碼來修正balance的值 //是以,balance的值是弗成能小於0 的 lock (this) { Console.WriteLine("Current Thread:"+Thread.CurrentThread.Name); //假如沒有lock症結字的掩護,那末能夠在履行完if的前提斷定以後 //別的一個線程卻履行了balance=balance-amount修正了balance的值 //而這個修正對這個線程是弗成見的,所以能夠招致這時候if的前提曾經不成立了 //然則,這個線程卻持續履行balance=balance-amount,所以招致balance能夠小於0 if (balance >= amount) { Thread.Sleep(5); balance = balance - amount; return amount; } else { return 0; // transaction rejected } } } internal void DoTransactions() { for (int i = 0; i < 100; i++) Withdraw(r.Next(-50, 100)); } } internal class Test { static internal Thread[] threads = new Thread[10]; public static void Main() { Account acc = new Account (0); for (int i = 0; i < 10; i++) { Thread t = new Thread(new ThreadStart(acc.DoTransactions)); threads[i] = t; } for (int i = 0; i < 10; i++) threads[i].Name=i.ToString(); for (int i = 0; i < 10; i++) threads[i].Start(); Console.ReadLine(); } } }
Monitor 類鎖定一個對象
當多線程公用一個對象時,也會湧現和公用代碼相似的成績,這類成績就不該該應用lock症結字了,這裡須要用到System.Threading中的一個類Monitor,我們可以稱之為監督器,Monitor供給了使線程同享資本的計劃。
Monitor類可以鎖定一個對象,一個線程只要獲得這把鎖才可以對該對象停止操作。對象鎖機制包管了在能夠惹起凌亂的情形下一個時辰只要一個線程可以拜訪這個對象。
Monitor必需和一個詳細的對象相干聯,然則因為它是一個靜態的類,所以不克不及應用它來界說對象,並且它的一切辦法都是靜態的,不克不及應用對象來援用。上面代碼解釋了應用Monitor鎖定一個對象的情況:
...... Queue oQueue=new Queue(); ...... Monitor.Enter(oQueue); ......//如今oQueue對象只能被以後線程把持了 Monitor.Exit(oQueue);//釋放鎖
如上所示,當一個線程挪用Monitor.Enter()辦法鎖定一個對象時,這個對象就歸它一切了, 其它線程想要拜訪這個對象,只要期待它應用Monitor.Exit()辦法釋放鎖。為了包管線程終究都能釋放鎖,你可以把Monitor.Exit() 辦法寫在try-catch-finally構造中的finally代碼塊裡。
關於任何一個被Monitor鎖定的對象,內存中都保留著與它相干的一些信息:
其一是如今持有鎖的線程的援用;
其二是一個准備隊列,隊列中保留了曾經預備好獲得鎖的線程;
其三是一個期待隊列,隊列中保留著以後正在期待這個對象狀況轉變的隊列的援用。
當具有對象鎖的線程預備釋放鎖時,它應用Monitor.Pulse()辦法告訴期待隊列中的第一個線程,因而該線程被轉移到准備隊列中,當對象鎖被釋放時,在准備隊列中的線程可以立刻取得對象鎖。
上面是一個展現若何應用lock症結字和Monitor類來完成線程的同步和通信的例子,也是一個典范的臨盆者與花費者成績。
這個例程中,臨盆者線程和花費者線程是瓜代停止的,臨盆者寫入一個數,花費者立刻讀取而且顯示(正文中引見了該法式的精要地點)。
用到的體系定名空間以下:
using System; using System.Threading;
起首,界說一個被操作的對象的類Cell,在這個類裡,有兩個辦法:ReadFromCell()和 WriteToCell。花費者線程將挪用ReadFromCell()讀取cellContents的內容而且顯示出來,臨盆者過程將挪用 WriteToCell()辦法向cellContents寫入數據。
示例以下:
public class Cell { int cellContents; // Cell對象裡邊的內容 bool readerFlag = false; // 狀況標記,為true時可以讀取,為false則正在寫入 public int ReadFromCell( ) { lock(this) // Lock症結字包管了甚麼,請年夜家看後面對lock的引見 { if (!readerFlag)//假如如今弗成讀取 { try { //期待WriteToCell辦法中挪用Monitor.Pulse()辦法 Monitor.Wait(this); } catch (SynchronizationLockException e) { Console.WriteLine(e); } catch (ThreadInterruptedException e) { Console.WriteLine(e); } } Console.WriteLine("Consume: {0}",cellContents); readerFlag = false; //重置readerFlag標記,表現花費行動曾經完成 Monitor.Pulse(this); //告訴WriteToCell()辦法(該辦法在別的一個線程中履行,期待中) } return cellContents; } public void WriteToCell(int n) { lock(this) { if (readerFlag) { try { Monitor.Wait(this); } catch (SynchronizationLockException e) { //當同步辦法(指Monitor類除Enter以外的辦法)在非同步的代碼區被挪用 Console.WriteLine(e); } catch (ThreadInterruptedException e) { //當線程在期待狀況的時刻中斷 Console.WriteLine(e); } } cellContents = n; Console.WriteLine("Produce: {0}",cellContents); readerFlag = true; Monitor.Pulse(this); //告訴別的一個線程中正在期待的ReadFromCell()辦法 } } }
上面界說臨盆者類 CellProd 和花費者類 CellCons ,它們都只要一個辦法ThreadRun(),以便在Main()函數中供給給線程的ThreadStart署理對象,作為線程的進口。
public class CellProd { Cell cell; // 被操作的Cell對象 int quantity = 1; // 臨盆者臨盆次數,初始化為1 public CellProd(Cell box, int request) { //結構函數 cell = box; quantity = request; } public void ThreadRun( ) { for(int looper=1; looper<=quantity; looper++) cell.WriteToCell(looper); //臨盆者向操尴尬刁難象寫入信息 } } public class CellCons { Cell cell; int quantity = 1; public CellCons(Cell box, int request) { //結構函數 cell = box; quantity = request; } public void ThreadRun( ) { int valReturned; for(int looper=1; looper<=quantity; looper++) valReturned=cell.ReadFromCell( );//花費者從操尴尬刁難象中讀守信息 } }
然後鄙人面這個類MonitorSample的Main()函數中,我們要做的就是創立兩個線程分離作為臨盆者和花費者,應用CellProd.ThreadRun()辦法和CellCons.ThreadRun()辦法對統一個Cell對象停止操作。
public class MonitorSample { public static void Main(String[] args) { int result = 0; //一個標記位,假如是0表現法式沒有失足,假如是1注解有毛病產生 Cell cell = new Cell( ); //上面應用cell初始化CellProd和CellCons兩個類,臨盆和花費次數均為20次 CellProd prod = new CellProd(cell, 20); CellCons cons = new CellCons(cell, 20); Thread producer = new Thread(new ThreadStart(prod.ThreadRun)); Thread consumer = new Thread(new ThreadStart(cons.ThreadRun)); //臨盆者線程和花費者線程都曾經被創立,然則沒有開端履行 try { producer.Start( ); consumer.Start( ); producer.Join( ); consumer.Join( ); Console.ReadLine(); } catch (ThreadStateException e) { //當線程由於所處狀況的緣由而不克不及履行被要求的操作 Console.WriteLine(e); result = 1; } catch (ThreadInterruptedException e) { //當線程在期待狀況的時刻中斷 Console.WriteLine(e); result = 1; } //雖然Main()函數沒有前往值,但上面這條語句可以向父過程前往履行成果 Environment.ExitCode = result; } }
在下面的例程中,同步是經由過程期待Monitor.Pulse()來完成的。起首臨盆者臨盆了一個值,而 統一時辰花費者處於期待狀況,直到收到臨盆者的“脈沖(Pulse)”告訴它臨盆曾經完成,爾後花費者進入花費狀況,而臨盆者開端期待花費者完成操作後將 挪用Monitor.Pulese()收回的“脈沖”。
它的履行成果很簡略:
Produce: 1
Consume: 1
Produce: 2
Consume: 2
Produce: 3
Consume: 3
...
...
Produce: 20
Consume: 20
現實上,這個簡略的例子曾經贊助我們處理了多線程運用法式中能夠湧現的年夜成績,只需融會懂得決線程間抵觸的根本辦法,很輕易把它運用到比擬龐雜的法式中去。
願望本文所述對年夜家的C#法式設計有所贊助。