前言:此篇就主要從博主使用過的幾種多執行緒的用法從應用層面大概介紹下。文中觀點都是博主個人的理解,如果有不對的地方望大家指正~~
1、多執行緒:使用多個處理控制程式碼同時對多個任務進行控制處理的一種技術。據博主的理解,多執行緒就是該應用的主執行緒任命其他多個執行緒去協助它完成需要的功能,並且主執行緒和協助執行緒是完全獨立進行的。不知道這樣說好不好理解,後面慢慢在使用中會有更加詳細的講解。
2、多執行緒的使用:
(1)最簡單、最原始的使用方法:Thread oGetArgThread = new Thread(new ThreadStart(() =>{});這種用法應該大多數人都使用過,引數為一個ThreadStart型別的委託。將ThreadStart轉到定義可知:
1 |
public delegate void ThreadStart(); |
它是一個沒有引數,沒有返回值的委託。所以他的使用如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
static void Main(string[] args) { Thread oGetArgThread = new Thread(new ThreadStart(Test)); oGetArgThread.IsBackground = true; oGetArgThread.Start(); for (var i = 0; i < 1000000; i++) { Console.WriteLine("主執行緒計數" + i); //Thread.Sleep(100); } } private static void Test() { for (var i = 0; i < 1000000; i++) { Console.WriteLine("後臺執行緒計數" + i); //Thread.Sleep(100); } } |
定義一個沒有引數沒有返回值的方法傳入該委託。當然也可以不定義方法寫成匿名方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
static void Main(string[] args) { Thread oGetArgThread = new Thread(new System.Threading.ThreadStart(() => { for (var i = 0; i < 1000000; i++) { Console.WriteLine("後臺執行緒計數" + i); //Thread.Sleep(100); } })); oGetArgThread.IsBackground = true; oGetArgThread.Start(); } |
這個和上面的意義相同。得到的結果如下:
說明主執行緒和後臺執行緒是互相獨立的。由系統排程資源去執行。
如果這樣那有人就要問了,如果我需要多執行緒執行的方法有引數或者有返回值或者既有引數又有返回值呢。。。彆著急我們來看看new Thread()的幾個建構函式:
1 2 3 4 |
public Thread(ParameterizedThreadStart start); public Thread(ThreadStart start); public Thread(ParameterizedThreadStart start, int maxStackSize); public Thread(ThreadStart start, int maxStackSize); |
轉到定義可知引數有兩類,一類是無參無返回值的委託,另一類是有參無返回值的委託。對於有引數的委託使用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
static void Main(string[] args) { Thread oThread = new Thread(new ParameterizedThreadStart(Test2)); oThread.IsBackground = true; oThread.Start(1000); } private static void Test2(object Count) { for (var i = 0; i < (int)Count; i++) { Console.WriteLine("後臺執行緒計數" + i); //Thread.Sleep(100); } } |
對於有參又有返回值的委託,很顯然使用new Thread()這種方式是沒有解決方案的。其實對於有參又有返回值的委託可以使用非同步來實現:
1 2 3 4 5 6 7 8 9 |
public delegate string MethodCaller(string name);//定義個代理 MethodCaller mc = new MethodCaller(GetName); string name = "my name";//輸入引數 IAsyncResult result = mc.BeginInvoke(name,null, null); string myname = mc.EndInvoke(result);//用於接收返回值 public string GetName(string name) // 函式 { return name; } |
關於這種方式還有幾點值得一說的是:
①Thread oGetArgThread = new Thread(new ThreadStart(Test));
oGetArgThread.Join();//主執行緒阻塞,等待分支執行緒執行結束,這一步看功能需求進行選擇,主要為了多個程式達到同步的效果
②執行緒的優先順序可以通過Thread物件的Priority屬性來設定,Priority屬性對應一個列舉:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public enum ThreadPriority { // 摘要: // 可以將 System.Threading.Thread 安排在具有任何其他優先順序的執行緒之後。 Lowest = 0, // // 摘要: // 可以將 System.Threading.Thread 安排在具有 Normal 優先順序的執行緒之後,在具有 Lowest 優先順序的執行緒之前。 BelowNormal = 1, // // 摘要: // 可以將 System.Threading.Thread 安排在具有 AboveNormal 優先順序的執行緒之後,在具有 BelowNormal 優先順序的執行緒之前。 // 預設情況下,執行緒具有 Normal 優先順序。 Normal = 2, // // 摘要: // 可以將 System.Threading.Thread 安排在具有 Highest 優先順序的執行緒之後,在具有 Normal 優先順序的執行緒之前。 AboveNormal = 3, // // 摘要: // 可以將 System.Threading.Thread 安排在具有任何其他優先順序的執行緒之前。 Highest = 4, } |
從0到4,優先順序由低到高。
③關於多個執行緒同時使用一個物件或資源的情況,也就是執行緒的資源共享,為了避免資料紊亂,一般採用.Net悲觀鎖lock的方式處理。
1 2 3 4 5 6 7 8 9 10 11 12 |
private static object oLock = new object(); private static void Test2(object Count) { lock (oLock) { for (var i = 0; i < (int)Count; i++) { Console.WriteLine("後臺執行緒計數" + i); //Thread.Sleep(100); } } } |
(2)Task方式使用多執行緒:這種方式一般用在需要迴圈處理某項業務並且需要得到處理後的結果。使用程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
List<Task> lstTaskBD = new List<Task>(); foreach (var bd in lstBoards) { var bdTmp = bd;//這裡必須要用一個臨時變數 var oTask = Task.Factory.StartNew(() => { var strCpBdCmd = "rm -Rf " + bdTmp.Path + "/*;cp -R " + CombineFTPPaths(FTP_EMULATION_BD_ROOT, "bd_correct") + "/* " + bdTmp.Path + "/"; oPlink.Run(bdTmp.EmulationServer.BigIP, bdTmp.EmulationServer.UserName, bdTmp.EmulationServer.Password, strCpBdCmd); Thread.Sleep(500); }); lstTaskBD.Add(oTask); } Task.WaitAll(lstTaskBD.ToArray());//等待所有執行緒只都行完畢 |
使用這種方式的時候需要注意這一句 var bdTmp = bd;這裡必須要用一個臨時變數,要不然多個bd物件容易串資料。如果有興趣可以除錯看看。這種方法比較簡單,就不多說了。當然Task物件的用法肯定遠不止如此,還涉及到任務的排程等複雜的邏輯。博主對這些東西理解有限,就不講解了。
(3)執行緒池的用法:一般由於考慮到伺服器的效能等問題,保證一個時間段內系統執行緒數量在一定的範圍,需要使用執行緒池的概念。大概用法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
public class CSpiderCtrl { //將執行緒池物件作為一個全域性變數 static Semaphore semaphore; public static void Run() { //1. 建立 SuperLCBB客戶端物件 var oClient = new ServiceReference_SuperLCBB.SOAServiceClient(); //2.初始化的時候new最大的執行緒池個數255(這個數值根據實際情況來判斷,如果伺服器上面的東西很少,則可以設定大點) semaphore = new Semaphore(250, 255); CLogService.Instance.Debug("又一輪定時採集..."); _TestBedGo(oClient); } //執行多執行緒的方法 private static void _TestBedGo(ServiceReference_SuperLCBB.SOAServiceClient oClient) { List<string> lstExceptPDUs = new List<string>(){ "SUPERLABEXP" }; var oTestBedRes = oClient.GetTestBedExceptSomePDU(lstExceptPDUs.ToArray(), true); if (CKVRes.ERRCODE_SUCCESS != oTestBedRes.ErrCode) { CLogService.Instance.Error("xxx"); return; } var lstTestBed = oTestBedRes.ToDocumentsEx(); System.Threading.Tasks.Parallel.ForEach(lstTestBed, (oTestBed) => { //一次最多255個執行緒,超過255的必須等待執行緒池釋放一個執行緒出來才行 semaphore.WaitOne(); //CLogService.Instance.Info("開始採集測試床:" + oTestBed[TBLTestBed.PROP_NAME]); //Thread.Sleep(2000); var strTestBedName = oTestBed[TBLTestBed.PROP_NAME] as string; var strSuperDevIP = oTestBed[TBLTestBed.PROP_SUPERDEVIP] as string; var strTestBedGID = oTestBed[TBLTestBed.PROP_GID] as string; var strPdu = oTestBed[TBLTestBed.PROP_PDUGID] as string; Thread.Sleep(new Random().Next(1000, 5000)); var oGetRootDevicesByTestBedGIDRes = oClient.GetRootDevicesByTestBedGID(strTestBedGID); CLogService.Instance.Debug(strPdu + "——測試床Name:" + strTestBedName + "開始"); Stopwatch sp = new Stopwatch(); sp.Start(); if (oGetRootDevicesByTestBedGIDRes.ErrCode != CKVRes.ERRCODE_SUCCESS || oGetRootDevicesByTestBedGIDRes.Documents.Count < 2) { CLogService.Instance.Debug("shit -- 3實驗室中測試床Name:" + strTestBedName + "2完成異常0"); //這裡很重要的一點,每一次return 前一定要記得釋放執行緒,否則這個一直會佔用資源 semaphore.Release(); return; } var strXML = oGetRootDevicesByTestBedGIDRes.Documents[0]; var strExeName = oGetRootDevicesByTestBedGIDRes.Documents[1]; //var strExeName = "RateSpider"; var oSuperDevClient = new SuperDevClient(CSuperDev.ENDPOINT, string.Format(CSuperDev.SuperDevURL, strSuperDevIP)); try { oSuperDevClient.IsOK(); } catch (Exception) { CLogService.Instance.Error("測試床Name:" + strTestBedName + "異常,外掛沒起"); semaphore.Release(); return; } //2.3.1.請求SuperDev.Server(SuperDevIP),傳送Run(XML和Exename) var oRunExeRes = new CKVRes(); try { oRunExeRes = oSuperDevClient.RunExeEx(strExeName, false, new string[] { strXML }); } catch { //CLogService.Instance.Debug("測試床Name:" + strTestBedName + "異常:" + ex.Message); } sp.Stop(); CLogService.Instance.Debug(strPdu + "——測試床Name:" + strTestBedName + "完成時間" + sp.Elapsed); //每一個執行緒完畢後記得釋放資源 semaphore.Release(); }); } } |
需要注意:Semaphore物件的數量需要根據伺服器的效能來設定;System.Threading.Tasks.Parallel.ForEach這種方式表示同時啟動lstTestBed.Length個執行緒去做一件事情,可以理解為
1 2 3 4 |
foreach(var oTestbed in lstTestBed) { Thread oThread=new Thread(new ThreadStart({ ...})); } |
(4) 多執行緒裡面還有一個值得一說的SpinWait類,用於提供對基於自旋的等待的支援。也就是說支援重複執行一個委託,知道滿足條件就返回,我們來看它的用法:
1 2 3 |
public static void SpinUntil(Func<bool> condition); public static bool SpinUntil(Func<bool> condition, int millisecondsTimeout); public static bool SpinUntil(Func<bool> condition, TimeSpan timeout); |
這個方法有三個建構函式,後兩個需要傳入一個時間,表示如果再規定的時間內還沒有返回則自動跳出,防止死迴圈。
1 2 3 4 5 6 7 8 9 10 11 |
SpinWait.SpinUntil(() => { bIsworking = m_oClient.isworking(new isworking()).result; return bIsworking == false; }, 600000); //如果等了10分鐘還在跳纖則跳出 if (bIsworking) { oRes.ErrCode = "false交換機跳纖時間超過10分鐘,請檢查異常再操作"; return oRes; } |
博主使用過的多執行緒用法大概就這麼三大類,當然這些其中還涉及很多細節性的東西,博主原來使用這些的時候經常出現各種莫名的問題,可能還是沒用好的原因,對這些東西理解還不夠深刻。如果大家也遇到類似的問題可以拿出來探討!!