線程也瘋狂----線程同步(1)

来源:http://www.cnblogs.com/dongqinnanren/archive/2017/02/09/6370726.html
-Advertisement-
Play Games

前言 當線程池的線程阻塞時,線程池會創建額外的線程,而創建、銷毀和調度線程所需要相當昂貴的記憶體資源,另外,很多的開發人員看見自己程式的線程沒有做任何有用的事情時習慣創建更多的線程,為了構建可伸縮、響應靈敏的程式,我們在前面介紹了線程也瘋狂 非同步編程。 但是非同步編程同樣也存在著很嚴重的問題,如果兩個不 ...


前言

當線程池的線程阻塞時,線程池會創建額外的線程,而創建、銷毀和調度線程所需要相當昂貴的記憶體資源,另外,很多的開發人員看見自己程式的線程沒有做任何有用的事情時習慣創建更多的線程,為了構建可伸縮、響應靈敏的程式,我們在前面介紹了線程也瘋狂-----非同步編程

但是非同步編程同樣也存在著很嚴重的問題,如果兩個不同的線程訪問相同的變數和數據,按照我們非同步函數的實現方式,不可能存在兩個線程同時訪問相同的數據,這個時候我們就需要線程同步。多個線程同時訪問共用數據的時,線程同步能防止數據損壞,之所以強調同時這個概念,因為線程同步本質就是計時問題。

非同步和同步是相對的,同步就是順序執行,執行完一個再執行下一個,需要等待、協調運行。非同步就是彼此獨立,在等待某事件的過程中繼續做自己的事,不需要等待這一事件完成後再工作。線程就是實現非同步的一個方式。非同步是讓調用方法的主線程不需要同步等待另一線程的完成,從而可以讓主線程乾其它的事情。

 

基元用戶模式和內核模式構造

基礎概念

基元:可以在代碼中使用的簡單的構造

用戶模式:通過特殊的CPU指令協調線程,操作系統永遠檢測不到一個線程在基元用戶模式的構造上阻塞。

內核模式:由windows自身提供,在應用程式的線程中調用由內核實現的函數。

用戶模式構造

易變構造

 C#編譯器、JIT編譯器和CPU都會對代碼進行優化,它們儘量保證保留我們的意圖,但是從多線程的角度出發,我們的意圖並不一定會得到保留,下麵舉例說明:

 1  static void Main(string[] args)
 2         {
 3            Console.WriteLine("讓worker函數運行5s後停止");
 4            var t = new Thread(Worker);
 5             t.Start();
 6             Thread.Sleep(5000);
 7             stop = true;
 8 
 9             Console.ReadLine();
10         }
11 
12         private static bool stop = false;
13 
14         private static void Worker(object obj)
15         {
16             int x = 0;
17             while (!stop)
18             {
19                 x++;
20             }
21             Console.WriteLine("worker函數停止x={0}",x);
22         }

編譯器如果檢查到stop為false,就生成代碼來進入一個無限迴圈,併在迴圈中一直遞增x,所以優化迴圈很快完成,但是編譯器只檢測stop一次,並不是每次都會檢測。

例子2---兩個線程同時訪問:

 1 class test
 2     {
 3         private static int m_flag = 0;
 4 
 5         private static int m_value = 0;
 6 
 7         public static void Thread1(object obj)
 8         {
 9             m_value = 5;
10             m_flag = 1;
11 
12         }
13 
14         public static void Thread2(object obj)
15         {
16             if (m_flag == 1)
17                 Console.WriteLine("m_value = {0}", m_value);
18         }
19 
20         //多核CPU機器才會出現線程同步問題
21         public void Exec()
22         {
23             var thread1 = new Thread(Thread1);
24             var thread2 = new Thread(Thread2);
25             thread1.Start();
26             thread2.Start();
27             Console.ReadLine();
28         }
29     }

程式在執行的時候,編譯器必須將變數m_flag和m_value從RAM讀入CPU寄存器,RAM先傳遞m_value的值0,thread1把值變為5,但是thread2並不知道thread2仍然認為值為0,這種問題一般來說發生在多核CPU的概率大一些,應該CPU越多,多個線程同時訪問資源的幾率就越大。

關鍵字volatile,作用禁止C#編譯器、JTP編譯器和CPU執行的一些優化,如果做用於變數後,將不允許欄位緩存到CPU的寄存器中,確保欄位的讀寫都在RAM中進行。

 

互鎖構造

System.Threading.Interlocked類中的每個方法都執行一次原子的讀取以及寫入操作,調用某個Interlocked方法之前的任何變數寫入都在這個Interlocked方法調用之前執行,而調用之後的任何變數讀取都在這個調用之後讀取。

Interlocked方法主要是對INT32變數進行靜態操作Add、Decrement、Compare、Exchange、CompareChange等方法,也接受object、Double等類型的參數。

原子操作:是指不會被線程調度機制打斷的操作;這種操作一旦開始,就一直運行到結束,中間不會有任何 context switch (切換到另一個線程)。

代碼演示:

說明:通過Interlocked的方法非同步查詢幾個web伺服器,並同時返回數據,且結果只執行一次。

//上報狀態類型
    enum CoordinationStatus
    {
        Cancel,
        Timeout,
        AllDone
    }
 1  class AsyncCoordinator
 2     {
 3         //AllBegun 內部調用JustEnded來遞減它
 4         private int _mOpCount = 1;
 5 
 6         //0=false,1=true
 7         private int _mStatusReported = 0;
 8 
 9         private Action<CoordinationStatus> _mCallback;
10 
11         private Timer _mTimer;
12 
13         //發起一個操作之前調用
14         public void AboutToBegin(int opsToAdd = 1)
15         {
16            Interlocked.Add(ref _mOpCount, opsToAdd);
17         }
18 
19         //處理好一個操作的結果之後調用
20         public void JustEnded()
21         {
22             if (Interlocked.Decrement(ref _mOpCount) == 0)
23             {
24                 ReportStatus(CoordinationStatus.AllDone);
25             }        
26         }
27 
28         //該方法必須在發起所有操作後調用
29         public void AllBegin(Action<CoordinationStatus> callback, int timeout = Timeout.Infinite)
30         {
31             _mCallback = callback;
32             if (timeout != Timeout.Infinite)
33             {
34                 _mTimer = new Timer(TimeExpired, null, timeout, Timeout.Infinite);
35                 JustEnded();
36             }
37         }
38 
39         private void TimeExpired(object o)
40         {
41             ReportStatus(CoordinationStatus.Timeout);
42         }
43 
44         public void Cancel()
45         {
46             ReportStatus(CoordinationStatus.Cancel);
47         }
48 
49         private void ReportStatus(CoordinationStatus status)
50         {
51             //如果狀態從未報告過,就報告它,否則就忽略它,只調用一次
52             if (Interlocked.Exchange(ref _mStatusReported, 1) == 0)
53             {
54                 _mCallback(status);
55             }         
56         }
57     }
 1 class MultiWebRequest
 2     {
 3         //輔助類 用於協調所有的非同步操作
 4         private AsyncCoordinator _mac = new AsyncCoordinator();
 5 
 6         protected Dictionary<string,object> _mServers = new Dictionary<string, object>
 7         {
 8             {"http://www.baidu.com",null},{"http://www.Microsoft.com",null},{"http://www.cctv.com",null},
 9             {"http://www.souhu.com",null},{"http://www.sina.com",null},{"http://www.tencent.com",null},
10             {"http://www.youku.com",null}
11         };
12 
13         private Stopwatch sp;
14         public MultiWebRequest(int timeout = Timeout.Infinite)
15         {
16             sp = new Stopwatch();
17             sp.Start();
18             //通過非同步方式一次性發起請求
19             var httpclient = new HttpClient();
20 
21             foreach (var server in _mServers.Keys)
22             {
23                 _mac.AboutToBegin(1);
24 
25                 httpclient.GetByteArrayAsync(server).ContinueWith(task => ComputeResult(server, task));
26             }
27             _mac.AllBegin(AllDone,timeout);
28             Console.WriteLine("");
29         }
30 
31         private void ComputeResult(string server, Task<Byte[]> task)
32         {
33             object result;
34             if (task.Exception != null)
35             {
36                 result = task.Exception.InnerException;
37             }
38             else
39             {
40                 //線程池處理IO
41                 result = task.Result.Length;
42             }
43 
44             //保存返回結果的長度
45             _mServers[server] = result;
46 
47             _mac.JustEnded();
48         }
49 
50         public void Cancel()
51         {
52             _mac.Cancel();
53         }
54 
55         private void AllDone(CoordinationStatus status)
56         {
57             sp.Stop();
58             Console.WriteLine("響應耗時總計{0}",sp.Elapsed);
59             switch (status)
60             {
61                 case CoordinationStatus.Cancel:
62                     Console.WriteLine("操作取消");
63                     break;
64                 case CoordinationStatus.AllDone:
65                     Console.WriteLine("操作完成,完成的結果如下");
66                     foreach (var server in _mServers)
67                     {
68                         Console.WriteLine("{0}",server.Key);
69                         object result = server.Value;
70                         if (result is Exception)
71                         {
72                             Console.WriteLine("錯誤原因{0}",result.GetType().Name);
73                         }
74                         else
75                         {
76                             Console.WriteLine("返回位元組數為:{0}",result);
77                         }
78                     }
79                     break;
80                 case CoordinationStatus.Timeout:
81                     Console.WriteLine("操作超時");
82                     break;
83                 default:
84                     throw new ArgumentOutOfRangeException("status", status, null);
85             }
86         }
87     }

非常建議大家參考一下以上代碼,我在對伺服器進行訪問時,也會常常參考這個模型。

簡單的自旋鎖

 1 class SomeResource
 2     {
 3          private SimpleSpinLock s1 = new SimpleSpinLock();
 4 
 5         public void AccessResource()
 6         {
 7             s1.Enter();
 8             //一次是有一個線程才能進入訪問
 9             s1.Leave();
10 
11         }
12     }
13 
14     class SimpleSpinLock
15     {
16         private int _mResourceInUse;
17 
18         public void Enter()
19         {
20             while (true)
21             {
22                 if(Interlocked.Exchange(ref _mResourceInUse,1)==0)
23                     return;
24             }
25         }
26 
27         public void Leave()
28         {
29             Volatile.Write(ref _mResourceInUse,1);
30         }
31     }

這就是一個線程同步鎖的簡單實現,這種鎖的最大問題在於,存在競爭的情況下會造成線程的“自旋”,這會浪費CPU的寶貴時間,組織CPU做更多的工作,因此,這種自旋鎖應該用於保護那些執行的非常快的代碼。

 

下篇我們將繼續講解線程同步,內核模式構造和混合線程同步構造,希望這些內容可以幫助到大家一起成長,如果發現博客有什麼錯誤請及時提出寶貴意見,以免造成誤導!

 

 

 

 

 


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

-Advertisement-
Play Games
更多相關文章
  • 1,如何進行單元測試呢,打開vs 新建一個項目 然後在解決方案右鍵點擊,如下圖所示: 2,左側點擊 測試 -》單元測試項目 3)點擊確定,如下圖 4)在當前代碼上右鍵點擊,調試 或者運行測試 ok 單元測試就好了,歡迎留言 ...
  • AO的版本為10.2,開發的語言是C#。柵格數據來源IRasterDataset介面。 第一次寫,同時也做一個記錄 ...
  • 5. 完整的自定義依賴屬性 5.1 定義 以上代碼為一個相對完成的依賴屬性例子(還有一些功能比較少用就不寫出了),從這段代碼可以看出,自定義依賴屬性的步驟如下: 1. 註冊依賴屬性並生成依賴屬性標識符。依賴屬性標識符為一個public static readonly DependencyProper ...
  • //關閉模態框 window.parent.$('#myModal').modal('hide'); //修改成功後刷新table表格 window.parent.$('#reportTable').bootstrapTable('refresh'); ...
  • 前臺代碼 <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" > <div class="modal-dialog" st ...
  • 經過幾天學習,寫出了一個簡單的winform應用程式,貼出源碼,以備不時之需。 軟體啟動後的界面如下圖所示: 如圖,該程式由6個label、8個comboBox、8個textBox和4個button組成。右邊4個textBox設置ReadOnly屬性為true。 軟體啟動時,可以讓comboBox顯 ...
  • 1、啥是RESTFul 服務 在我們創建簡單小程式前,先來學習下RESTFul 服務。RESTFul服務就是遵循了 Representational State Transfer(可以參考http://blog.csdn.net/zhruifei/article/details/50633495) ...
  • 1.nuget中添加包EF和MySql.Data.Entity 2.config文件添加如下配置 1.配置entitframework節點(一般安裝EF時自動添加) 2.配置system.data節點(一般安裝MySql.Data.Entity時自動添加) 3.添加連接串節點(以實際情況修改庫名、密 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...