C#多線程之線程同步3

来源:http://www.cnblogs.com/yonghuacui/archive/2016/12/23/6213519.html
-Advertisement-
Play Games

在上一篇C#多線程之線程同步2中,我們主要學習了AutoResetEvent構造、ManualResetEventSlim構造和CountdownEvent構造,在這一篇中,我們將學習Barrier構造、ReaderWriterLockSlim構造和SpinWait構造。 七、使用Barrier構造 ...


  在上一篇C#多線程之線程同步2中,我們主要學習了AutoResetEvent構造、ManualResetEventSlim構造和CountdownEvent構造,在這一篇中,我們將學習Barrier構造、ReaderWriterLockSlim構造和SpinWait構造。

七、使用Barrier構造

  在這一小節中,我們將學習一個比較有意思的同步構造:Barrier。Barrier構造可以幫助我們控制多個等待線程達到指定數量後,才發送通知信號,然後所有等待線程才能繼續執行,並且在每次等待線程達到指定數量後,還能執行一個回調方法。具體步驟如下所示:

1、使用Visual Studio 2015創建一個新的控制台應用程式。

2、雙擊打開“Program.cs”文件,編寫代碼如下所示:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 using static System.Threading.Thread;
 5 
 6 namespace Recipe07
 7 {
 8     class Program
 9     {
10         static Barrier barrier = new Barrier(2, b => WriteLine($"End of phase {b.CurrentPhaseNumber + 1}"));
11 
12         static void PlayMusic(string name, string message, int seconds)
13         {
14             for(int i = 1; i < 3; i++)
15             {
16                 WriteLine("----------------------------------------------");
17                 Sleep(TimeSpan.FromSeconds(seconds));
18                 WriteLine($"{name} starts to {message}");
19                 Sleep(TimeSpan.FromSeconds(seconds));
20                 WriteLine($"{name} finishes to {message}");
21                 barrier.SignalAndWait();
22             }
23             
24         }
25 
26         static void Main(string[] args)
27         {
28             var t1 = new Thread(() => PlayMusic("the guitarist", "play an amazing solo", 5));
29             var t2 = new Thread(() => PlayMusic("the singer", "sing his song", 2));
30 
31             t1.Start();
32             t2.Start();
33         }
34     }
35 }

3、運行該控制台應用程式,運行效果如下圖所示:

  在第10行代碼處,我們創建了一個Barrier的實例barrier,並給其構造方法的“participantCount”參數賦值為2,表示barrier參與線程的數量為2,也就是說要有2個線程達到阻塞後,barrier才發送通知信號,其阻塞線程才能繼續執行。第二個參數“postPhaseAction”是一個Action類型的委托,表示當阻塞線程達到規定數量後要執行的回調方法。

  在第28~29行代碼處,我們創建了2個線程t1和t2,用於執行“PlayMusic”方法。t2線程首先執行到第21行代碼處,在這一行代碼中,我們線上程t2中調用了barrier的“SignalAndWait”方法,等待參與數量的線程達到構造方法指定的數量2時,才能繼續執行,因為,在t2線程調用該方法時,只有一個線程t2被阻塞,沒有達到規定數量2,所以,t2線程不能繼續執行。當t1線程執行到第21行代碼處時,也調用了barrier的“SignalAndWait”方法,這個時候等待線程的數量達到規定的數量2,所以t1和t2線程都能繼續執行,並且在barrier的構造方法的第二個參數指定的回調方法也被執行。

  當兩個線程執行“PlayMusic”方法的第二次迴圈時,過程與第一次一樣,不在描述。

八、使用ReaderWriterLockSlim構造

  在這一小節中,我們將學習如何使用ReaderWriterLockSlim構造來線程安全地使用多線程讀寫集合中的數據。具體步驟如下所示:

1、使用Visual Studio 2015創建一個新的控制台應用程式。

2、雙擊打開“Program.cs”文件,編寫代碼如下所示:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Threading;
 4 using static System.Console;
 5 using static System.Threading.Thread;
 6 
 7 namespace Recipe08
 8 {
 9     class Program
10     {
11         // 表示用於管理資源訪問的鎖定狀態,可實現多線程讀取或進行獨占式寫入訪問
12         static ReaderWriterLockSlim rw = new ReaderWriterLockSlim();
13         static Dictionary<int, int> items = new Dictionary<int, int>();
14 
15         static void Read()
16         {
17             WriteLine("Reading contents of a dictionary");
18             while (true)
19             {
20                 try
21                 {
22                     // 嘗試進入讀取模式鎖定狀態
23                     rw.EnterReadLock();
24                     foreach(var key in items.Keys)
25                     {
26                         Sleep(TimeSpan.FromSeconds(0.1));
27                     }
28                 }
29                 finally
30                 {
31                     // 減少讀取模式的遞歸計數,併在生成的計數為 0(零)時退出讀取模式
32                     rw.ExitReadLock();
33                 }
34             }
35         }
36 
37         static void Write(string threadName)
38         {
39             while (true)
40             {
41                 try
42                 {
43                     int newKey = new Random().Next(250);
44                     // 嘗試進入可升級模式鎖定狀態
45                     rw.EnterUpgradeableReadLock();
46                     if (!items.ContainsKey(newKey))
47                     {
48                         try
49                         {
50                             // 嘗試進入寫入模式鎖定狀態
51                             rw.EnterWriteLock();
52                             items[newKey] = 1;
53                             WriteLine($"New key {newKey} is added to a dictionary by a {threadName}");
54                         }
55                         finally
56                         {
57                             // 減少寫入模式的遞歸計數,併在生成的計數為 0(零)時退出寫入模式
58                             rw.ExitWriteLock();
59                         }
60                     }
61                     Sleep(TimeSpan.FromSeconds(0.1));
62                 }
63                 finally
64                 {
65                     // 減少可升級模式的遞歸計數,併在生成的計數為 0(零)時退出可升級模式
66                     rw.ExitUpgradeableReadLock();
67                 }
68             }
69         }
70 
71         static void Main(string[] args)
72         {
73             new Thread(Read) { IsBackground = true }.Start();
74             new Thread(Read) { IsBackground = true }.Start();
75             new Thread(Read) { IsBackground = true }.Start();
76 
77             new Thread(() => Write("Thread 1")) { IsBackground = true }.Start();
78             new Thread(() => Write("Thread 2")) { IsBackground = true }.Start();
79 
80             Sleep(TimeSpan.FromSeconds(20));
81         }
82     }
83 }

3、運行該控制台應用程式,運行效果(每次運行效果可能不同)如下圖所示:

  在第73~75行代碼處,我們創建了3個後臺線程來讀取集合中的數據。在第77~78行代碼處,我們創建了2個後臺線程向集合中寫入數據。為了線程安全地對集合進行操作,我們使用為此場景專門設計的ReaderWriterLockSlim構造。該構造有兩種類型的鎖:讀取模式鎖和寫入模式鎖。讀取模式鎖允許多線程讀取數據,寫入模式鎖阻塞其他線程的每一個操作直到寫入模式鎖被釋放為止。

  有一個非常有趣的場景,當我們想獲得一個讀取模式鎖從集合中讀取一些數據,並根據這些數據獲得一個寫入模式鎖以更新集合時,如果我們立即就獲得鎖定模式鎖的話不僅消耗的時間多,而且還不允許我們讀取數據,因為當我們獲得一個寫入模式鎖的時候,集合就被鎖定了。為了儘量減少這種時間的浪費,我們可以使用“EnterUpgradeableReadLock”方法獲得讀取模式鎖來讀取數據,如果讀取完畢數據後,我們發現需要更新底層集合,那麼我們可以使用“EnterWriteLock”升級我們的鎖,然後快速執行寫入操作並使用“ExitWriteLock”釋放寫入模式鎖,最後使用“ExitUpgradeableReadLock”釋放可升級模式鎖。

  在上述代碼中,我們獲得一個隨機數,然後獲得一個讀取模式鎖,並檢查該隨機數是否已在集合中存在,如果不存在,我們升級該讀取模式鎖為寫入模式鎖,然後向集合中添加一個新的key。使用try/finally塊是一個比較好的方式,它可以保證我們總能釋放鎖獲得的鎖。

九、使用SpinWait構造

  在這一小節中,我們將學習如何在不涉及kernel-mode構造的情況下等待一個線程的執行。另外還將介紹SpinWait構造,該構造是一種混合同步構造,主要用於設計在用戶模式中等待一段時間後,然後將其切換到內核模式,以節省CUP時間。具體步驟如下所示:

1、使用Visual Studio 2015創建一個新的控制台應用程式。

2、雙擊打開“Program.cs”文件,編寫代碼如下所示:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 using static System.Threading.Thread;
 5 
 6 namespace Recipe09
 7 {
 8     class Program
 9     {
10         static volatile bool isCompleted = false;
11 
12         static void UserModeWait()
13         {
14             while (!isCompleted)
15             {
16                 Write(".");
17             }
18             WriteLine();
19             WriteLine("Waiting is complete");
20         }
21 
22         static void HybridSpinWait()
23         {
24             // 提供對基於自旋的等待的支持
25             var w = new SpinWait();
26             while (!isCompleted)
27             {
28                 // 執行單一自旋
29                 w.SpinOnce();
30                 // 獲取對 System.Threading.SpinWait.SpinOnce 的下一次調用是否將產生處理器,同時觸發強制上下文切換
31                 WriteLine(w.NextSpinWillYield);
32             }
33             WriteLine("Waiting is complete");
34         }
35 
36         static void Main(string[] args)
37         {
38             var t1 = new Thread(UserModeWait);
39             var t2 = new Thread(HybridSpinWait);
40 
41             WriteLine("Running user mode waiting");
42             t1.Start();
43             Sleep(20);
44             isCompleted = true;
45             Sleep(TimeSpan.FromSeconds(1));
46             isCompleted = false;
47             WriteLine("Running hybrid SpinWait construct waiting");
48             t2.Start();
49             Sleep(5);
50             isCompleted = true;
51         }
52     }
53 }

3、運行該控制台應用程式,運行效果(每次運行效果可能不同)如下圖所示:

  在上述程式中,我們創建了一個線程執行一個無線迴圈20毫秒,直到在主線程中將isCompleted變數設置為true。我們可以將此時間設置為20-30秒,然後打開任務管理器,我們可以看到CPU的使用率比較高。

  我們使用volatile關鍵字聲明瞭一個名為“isCompleted”的靜態欄位。volatile 關鍵字指示一個欄位可以由多個同時執行的線程修改。聲明為 volatile 的欄位不受編譯器優化(假定由單個線程訪問)的限制。這樣可以確保該欄位在任何時間呈現的都是最新的值。

  然後,我們使用SpinWait版本,在第29行代碼處,我們調用了SpinWait的“SpinOnce”方法,執行一次自旋。當SpinWait自旋達到一定次數後,如果有必要當前線程會讓出底層的時間片並觸發上下文切換。在這個版本中,如果我們將第49行代碼的等待時間修改為20~30秒,然後打開任務管理器,可以發現CPU使用率是比較低的。

  至此,關於線程同步的知識就學習到這兒!

  源碼下載


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 很高興能再次和大家分享webapi介面的相關文章,本篇將要講解的是如何在應用中調用webapi介面;對於大部分做內部管理系統及類似系統的朋友來說很少會去調用別人的介面,因此可能在這方面存在一些困惑,希望本篇分享文章內容能給您們帶來幫助或者學習,希望大家喜歡,也希望各位多多掃碼支持和點贊謝謝: » 簡 ...
  • 吐槽…使用清理軟體整理電腦要註意,不要清理的“太狠”,不然你會受傷的! C#中的Substring() 示例 實現代碼 using System;using System.Collections.Generic;using System.Linq;using System.Text;using Sy ...
  • 支持並尊重原創!原文地址:http://jingyan.baidu.com/article/2c8c281deb79ed0008252af1.html 判斷一個字元是不是漢字通常有三種方法,第1種用 ASCII 碼判斷,第2種用漢字的 UNICODE 編碼範圍判 斷,第3種用正則表達式判斷,下麵是具 ...
  • 官網: "http://mahapps.com/" github: "https://github.com/MahApps/MahApps.Metro" ...
  • 一、借鑒說明 1.《Head First Design Patterns》(中文名《深入淺出設計模式》) 2.維基百科,觀察者模式,https://zh.wikipedia.org/wiki/%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F 3.MSDN,e... ...
  • public class WriteLog { /// /// 創建日誌文件 /// /// 異常類 public static void CreateLog(Exception ex) { string path = A... ...
  • private void button1_Click(object sender, EventArgs e) { double number1, number2; if (double.TryParse(txtNumber1.Text, out number1) == false) { Messag... ...
  • web部件是ASP.NET WebForm裡面的伺服器控制項,它涵蓋的內容比較多,鑒於這種狀況的話鄙人不打算深究下去了,只是局限於瞭解web.config配置裡面的配置內容則可。 那麼也得稍微說說啥是Web部件。引用MSDN的話:ASP.NET Web 部件是一組集成控制項,用於創建網站使最終用戶可以直 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...