C#多線程學習(三) 生產者和消費者

来源:https://www.cnblogs.com/-NickWang/archive/2018/01/27/8364934.html
-Advertisement-
Play Games

前面說過,每個線程都有自己的資源,但是代碼區是共用的,即每個線程都可以執行相同的函數。這可能帶來的問題就是幾個線程同時執行一個函數,導致數據的混亂,產生不可預料的結果,因此我們必須避免這種情況的發生。 C#提供了一個關鍵字lock,它可以把一段代碼定義為互斥段(critical section),互 ...


前面說過,每個線程都有自己的資源,但是代碼區是共用的,即每個線程都可以執行相同的函數。這可能帶來的問題就是幾個線程同時執行一個函數,導致數據的混亂,產生不可預料的結果,因此我們必須避免這種情況的發生。   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(-50100));         }     }        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
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize")]public static extern int SetProcessWorkingSetSize(IntPtr process, int minSize, int... ...
  • 在.Net 4.5中,通過async和await兩個關鍵字,引入了一種新的基於任務的非同步編程模型(TAP)。在這種方式下,可以通過類似同步方式編寫非同步代碼,極大簡化了非同步編程模型。如下式一個簡單的實例: 而之前的方式是這樣的: 也許前面這個例子不足以體現async和await帶來的優越性,下麵這個例 ...
  • 18 01 26在v2ex上看到一妹紙發的《身為一個 21 歲的年輕程式員,我已經腰突了(躺》,哈哈,感同身受,想到這幾天我左腿麻木持續了好幾天,前幾天屁股疼的只要坐下就站不起來,不過站著卻一點事沒有,然後堅持站了好幾天,目前屁股不疼,腿麻的毛病還沒有恢復。。。[後續] 接觸到的https http ...
  • 新學一詞:達克效應。引出一句:“無知要比知識更容易產生自信。”—— 查爾斯·達爾文 寫在前面 在三亞呆了半個月了,三亞的冬天好熱啊,讓我回憶起了放暑假時下午百無聊賴的時光 { 一睡一下午٩(๑❛ᴗ❛๑)۶ }。 算是從昨天晚上開始調試WebSocket,代碼是16年初正常老代碼copy過來的,不過C ...
  • 前言 Spread for Windows Forms是功能最為強大的表格控制項,擁有靈活開放的對象模型和50,000個以上的API,使得開發人員幾乎可以定製所有的元素和介面。但另一方面,因為Spread的介面非常多,也有一些開發人員反映Spread的入門不太容易。希望我們編寫的“快速入門”系列文章, ...
  • 從記憶體中載入的程式集,無路徑 IIS中路徑 protected void Page_Load(object sender, EventArgs e) { Response.Write("程式集路徑"+System.Reflection.Assembly.GetExecutingAssembly().... ...
  • 【01】淺談HTTP在WebApi開發中的運用 【02】聊聊WebApi體繫結構 【03】詳解WebApi如何傳遞參數 【04】詳解WebApi測試和PostMan 【05】淺談WebApi Cores 【06】詳解WebApi 異常處理 【07】用WebAPI寫個基於EF的CURD 【08】淺談W ...
  • 源自:https://segmentfault.com/q/1010000000534091?_ea=178721 Model:很簡單,就是業務邏輯相關的數據對象,通常從資料庫映射而來,我們可以說是與資料庫對應的model。 View:也很簡單,就是展現出來的用戶界面。 基本上,絕大多數軟體所做的工 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...