多線程(6)線程同步

来源:http://www.cnblogs.com/mcgrady/archive/2017/07/01/7081433.html
-Advertisement-
Play Games

使用多線程很容易,但是如果多個線程同時訪問一個共用資源時而不加以控制,就會導致數據損壞。所以多線程併發時,必須要考慮線程同步(或稱線程安全)的問題。 什麼是線程同步 多個線程同時訪問共用資源時,使多個線程順序(串列)訪問共用資源的機制。 註意: 1,共用資源,比如全局變數和靜態變數。 2,訪問,一般 ...


   使用多線程很容易,但是如果多個線程同時訪問一個共用資源時而不加以控制,就會導致數據損壞。所以多線程併發時,必須要考慮線程同步(或稱線程安全)的問題。 

什麼是線程同步

多個線程同時訪問共用資源時,使多個線程順序(串列)訪問共用資源的機制。 註意: 1,共用資源,比如全局變數和靜態變數。 2,訪問,一般指寫操作,讀操作無需考慮線程同步。 3,串列,指當一個線程正在訪問共用資源時,其它線程等待,直到該線程釋放鎖。

線程同步帶來哪些問題

如果能保證多個線程不會同時訪問共用資源,那麼就不需要考慮線程同步。 雖然線程同步能保證多線程同時訪問共用數據時線程安全,但是同時也會帶來以下問題: 1,使用起來繁瑣,因為必須找出代碼中所有可能由多個線程同時訪問的共用數據,並且要用額外的代碼將這些代碼包圍起來,獲取和釋放一個線程同步鎖,而一旦有一處忘記用鎖包圍,共用數據就會被損壞。 2,損害性能,因為獲取和釋放一個鎖是需要時間的。 3,可能會造成更多的線程被創建,由於線程同步鎖一次只允許一個線程訪問共用資源,當線程池線程試圖獲取一個暫時無法獲取的鎖時,線程池就會創建一個新的線程。 所以,要從設計上儘可能地避免線程同步,實在不能避免的再考慮線程同步。

線程同步的常用解決方案

1,鎖

包括lock關鍵字和Monitor類型。   使用lock關鍵字實現:  

 

 1 /// <summary>
 2 /// 線程同步計算器
 3 /// </summary>
 4 public class SyncCounter : CounterBase
 5 {
 6     /// <summary>
 7     /// 全局變數
 8     /// </summary>
 9     public int Result = 0;
10 
11     private static readonly object lockObj = new object();
12 
13     public override void Increase()
14     {
15         lock (lockObj)
16         {
17             Result++;
18         }
19     }
20 
21     public override void Decrease()
22     {
23         lock (lockObj)
24         {
25             Result--;
26         }
27     }
28 }
View Code 需要註意的是: 1,lock鎖定的對象必須是引用類型,不能是值類型。因為值類型傳入會發生裝箱,這樣每次lock的將是一個不同的對象,就沒有辦法實現多線程同步了。 2,避免使用public類型的對象,這樣很容易導致死鎖。因為其它代碼也有可能鎖定該對象。 3,避免鎖定字元串,因為字元串會被CLR暫留(也就是說兩個變數的字元串內容相同,.net會把暫留的字元串對象分配給變數),導致應用程式中鎖定的是同一個對象,造成死鎖。   使用Monitor實現:  
 1 /// <summary>
 2 /// 線程同步計算器
 3 /// </summary>
 4 public class SyncCounter : CounterBase
 5 {
 6     /// <summary>
 7     /// 全局變數
 8     /// </summary>
 9     public int Result = 0;
10 
11     private static readonly object lockObj = new object();
12 
13     public override void Increase()
14     {
15         Monitor.Enter(lockObj);
16         try
17         {
18             Result++;
19         }
20         finally
21         {
22             Monitor.Exit(lockObj);
23         }
24     }
25 
26     public override void Decrease()
27     {
28         Monitor.Enter(lockObj);
29         try
30         {
31             Result--;
32         }
33         finally
34         {
35             Monitor.Exit(lockObj);
36         }
37     }
38 }
View Code

完整代碼:

  1 namespace ConsoleApplication28
  2 {
  3     class Program
  4     {
  5         static void Main(string[] args)
  6         {
  7             //同時發起3個非同步線程
  8             Console.WriteLine("普通(非線程同步)計算器測試...");
  9             var normalCounter = new NormalCounter();
 10             var tasks = new List<Task>();
 11             var task1 = Task.Factory.StartNew(() =>
 12             {
 13                 TestCounter(normalCounter);
 14             });
 15             tasks.Add(task1);
 16 
 17             var task2 = Task.Factory.StartNew(() =>
 18             {
 19                 TestCounter(normalCounter);
 20             });
 21             tasks.Add(task2);
 22 
 23             var task3 = Task.Factory.StartNew(() =>
 24             {
 25                 TestCounter(normalCounter);
 26             });
 27             tasks.Add(task3);
 28 
 29 
 30             Task.WaitAll(tasks.ToArray());
 31             Console.WriteLine("NormalCounter.Result:" + normalCounter.Result);
 32             Console.WriteLine("*******************************************");
 33 
 34             Console.WriteLine("線程同步計算器測試...");
 35             var syncCounter = new SyncCounter();
 36             var tasks1 = new List<Task>();
 37             task1 = Task.Factory.StartNew(() =>
 38             {
 39                 TestCounter(syncCounter);
 40             });
 41             tasks1.Add(task1);
 42 
 43             task2 = Task.Factory.StartNew(() =>
 44             {
 45                 TestCounter(syncCounter);
 46             });
 47             tasks1.Add(task2);
 48 
 49             task3 = Task.Factory.StartNew(() =>
 50             {
 51                 TestCounter(syncCounter);
 52             });
 53             tasks1.Add(task3);
 54 
 55             Task.WaitAll(tasks1.ToArray());
 56             Console.WriteLine("SyncCounter.Result:" + syncCounter.Result);
 57 
 58             Console.ReadKey();
 59         }
 60 
 61         /// <summary>
 62         /// 
 63         /// </summary>
 64         /// <param name="counter"></param>
 65         static void TestCounter(CounterBase counter)
 66         {
 67             //100000次加減
 68             for (int i = 0; i < 100000; i++)
 69             {
 70                 counter.Increase();
 71                 counter.Decrease();
 72             }
 73         }
 74     }
 75 
 76     /// <summary>
 77     /// 計算器基類
 78     /// </summary>
 79     public abstract class CounterBase
 80     {
 81         /// <summary>
 82         /// 83         /// </summary>
 84         public abstract void Increase();
 85 
 86         /// <summary>
 87         /// 88         /// </summary>
 89         public abstract void Decrease();
 90     }
 91 
 92     /// <summary>
 93     /// 普通計算器
 94     /// </summary>
 95     public class NormalCounter : CounterBase
 96     {
 97         /// <summary>
 98         /// 全局變數
 99         /// </summary>
100         public int Result = 0;
101 
102         public override void Increase()
103         {
104             Result++;
105         }
106 
107         public override void Decrease()
108         {
109             Result--;
110         }
111 
112     }
113 
114     /// <summary>
115     /// 線程同步計算器
116     /// </summary>
117     public class SyncCounter : CounterBase
118     {
119         /// <summary>
120         /// 全局變數
121         /// </summary>
122         public int Result = 0;
123 
124         private static readonly object lockObj = new object();
125 
126         public override void Increase()
127         {
128             lock (lockObj)
129             {
130                 Result++;
131             }
132         }
133 
134         public override void Decrease()
135         {
136             lock (lockObj)
137             {
138                 Result--;
139             }
140         }
141     }
142 }
View Code

  

lock關鍵字揭密:

 通過查看lock關鍵字生成的IL代碼,如下圖:

從上圖可以得出以下結論:

lock關鍵字內部就是使用Monitor類(或者說lock關鍵字是Monitor的語法糖),使用lock關鍵字比直接使用Monitor更好,原因有二。

1,lock語法更簡潔。

2,lock確保了即使代碼拋出異常,也可以釋放鎖,因為在finally中調用了Monitor.Exit方法。 

2,信號同步

信號同步機制中涉及的類型都繼承自抽象類WaitHandle,這些類型有EventWaitHandle(類型化為AutoResetEvent、ManualResetEvent)和Semaphore以及Mutex。關係如下圖。

 

下麵是使用信號同步機制的一個簡單的例子,如下代碼:

 1 namespace WindowsFormsApplication1
 2 {
 3     public partial class Form1 : Form
 4     {
 5         //信號
 6         AutoResetEvent autoResetEvent = new AutoResetEvent(false);
 7 
 8         public Form1()
 9         {
10             InitializeComponent();
11 
12             CheckForIllegalCrossThreadCalls = false;
13         }
14 
15         /// <summary>
16         /// 開始
17         /// </summary>
18         /// <param name="sender"></param>
19         /// <param name="e"></param>
20         private void button1_Click(object sender, EventArgs e)
21         {
22             Task.Factory.StartNew(() => 
23             {
24                 this.richTextBox1.Text+="線程啟動..." + Environment.NewLine;
25                 this.richTextBox1.Text += "開始處理一些實際的工作" + Environment.NewLine;
26                 Thread.Sleep(3000);
27 
28                 this.richTextBox1.Text += "我開始等待別的線程給我信號,才願意繼續下去" + Environment.NewLine;
29                 autoResetEvent.WaitOne();
30                 this.richTextBox1.Text += "我繼續做一些工作,然後結束了!";
31             });
32         }
33 
34         /// <summary>
35         /// 信號同步
36         /// </summary>
37         /// <param name="sender"></param>
38         /// <param name="e"></param>
39         private void button2_Click(object sender, EventArgs e)
40         {
41             //給在autoResetEvent上等待的線程一個信號
42             autoResetEvent.Set();
43         }
44     }
45 }
View Code

運行效果:

1,線程阻塞,等待信號。

2,主線程發送信號,讓線程繼續執行。

3,線程安全的集合類

我們也可以通過使用.net提供的線程安全的集合類來保證線程安全。在命名空間:System.Collections.Concurrent下。 主要包括:
  • ConcurrentQueue 線程安全版本的Queue【常用】
  • ConcurrentStack線程安全版本的Stack
  • ConcurrentBag線程安全的對象集合
  • ConcurrentDictionary線程安全的Dictionary【常用】
 
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • KGTP 介紹 KGTP 是一個能在產品系統上實時分析 Linux 內核和應用程式(包括 Android)問題的全面動態跟蹤器。 使用 KGTP 不需要 在 Linux 內核上打 PATCH 或者重新編譯,只要編譯 KGTP 模塊並insmod 就可以。 其讓 Linux 內核提供一個遠程 GDB ...
  • 翻譯了grub2官方手冊的絕大部分內容,然後自己整理了一下。因為內容有點雜,所以章節安排上可能不是太合理,敬請諒解。 本文目錄: 1.1 基礎內容 1.2 安裝grub2 1.3 grub2配置文件 1.4 命令行和菜單項中的命令 1.5 幾個常見的內置變數 1.6 grub配置和安裝示例 1.7 ...
  • 終於可以愉快的用谷歌了,容我哭一會,下麵會闡述下樓主的心塞歷程。 linux系統裡面不能翻牆,簡直就是受罪。 準備工作: 1 一個virtualbox管理器,這個是免費,挺適合新手用,還有一個是VMware,功能更加強大,不過是收費軟體 。這兩個選一個都行。 2 安裝好Ubuntu16.04,下載包 ...
  • #聲明腳本 #!/bin/bash #列印" cfb "echo " cfb " #java環境變數JAVA_HOME=/usr/java/jdk1.8.0_11CLASSPATH=$JAVA_HOME/bin #指定需要執行jar包的位置,可自行設置 JARPATH=/home/haha/Desk ...
  • " 1、文件夾操作 " "1.1、DIR(directory)命令" "1.2、TREE 命令" "1.3、CD(change directory)命令" "1.4、MD(make directory)命令" "1.5、RD(remove directory)命令" " 2、文件操作 " "2.1、 ...
  • 一、引言 IOC-Invertion of Control,即控制反轉,是一種程式設計思想,世上本沒有路,走的人多了便有了路,本文將一步步帶你瞭解IOC設計思想的演進之路。 在學習IOC之前我們先初步瞭解幾個概念 依賴(Dependency):就是有聯繫,表示一個類依賴於另一個類 依賴倒置原則(DI ...
  • 我們在調試WEB程式的時候可以把本地web程式掛載到本地IIS,然後訪問程式,通過附加進程的方式(w3wp)來調試程式(個人非常喜歡的一種調試方式),還有一種比較傳統的方式就是通過VS自帶的F5來執行,但是感覺很蹩腳,如果修改c#代碼我們不得不停止當前程式然後重新編譯重新F5來啟動,感覺時間很浪費, ...
  • 多線程使用過程中,除了線程同步的問題要考慮外,異常處理也是經常要面對的事情。 預設主線程捕獲不到非同步線程的異常 如下代碼: 1 namespace ConsoleApplication29 2 { 3 class Program 4 { 5 static void Main(string[] arg ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...