那麼何時能用多線程? 任務能併發的時候c++
多線程能幹嗎?提高速度/優化用戶體驗數據庫
網站首頁:A數據庫 B接口 C分佈式服務 D搜索引擎,適合多線程併發,都完成後才能返回給用戶,須要等待WaitAll
列表頁:核心數據可能來自數據庫/接口服務/分佈式搜索引擎/緩存,多線程併發請求,哪一個先完成就用哪一個結果,其餘的就無論了api
現實實例緩存
多人合做開發---多線程--提高效率/性能安全
1 { 2 TaskFactory taskFactory = new TaskFactory(); 3 List<Task> taskList = new List<Task>(); 4 taskList.Add(taskFactory.StartNew(o=> Coding("A", " Portal"), "A")); 5 taskList.Add(taskFactory.StartNew(o=> Coding("B", " DBA"), "B")); 6 taskList.Add(taskFactory.StartNew(o=> Coding("C", " Client"), "C")); 7 taskList.Add(taskFactory.StartNew(o=> Coding("D", "Service"), "D")); 8 taskList.Add(taskFactory.StartNew(o=> Coding("E", " Wechat"), "E")); 9 10 //誰第一個完成,獲取一個紅包獎勵 11 taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"{t.AsyncState}開發完成,獲取個紅包獎勵{Thread.CurrentThread.ManagedThreadId.ToString("00")}")); 12 //實戰做業完成後,一塊兒慶祝一下 13 taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"開發都完成,一塊兒慶祝一下{Thread.CurrentThread.ManagedThreadId.ToString("00")}"))); 14 //ContinueWhenAny ContinueWhenAll 非阻塞式的回調;並且使用的線程多是新線程,也多是剛完成任務的線程,惟一不多是主線程 15 16 17 //阻塞當前線程,等着任意一個任務完成 18 Task.WaitAny(taskList.ToArray());//也能夠限時等待 19 Console.WriteLine("準備環境開始部署"); 20 //須要可以等待所有線程完成任務再繼續 阻塞當前線程,等着所有任務完成 21 Task.WaitAll(taskList.ToArray()); 22 Console.WriteLine("5個模塊所有完成後,集中調試"); 23 24 //Task.WaitAny WaitAll都是阻塞當前線程,等任務完成後執行操做 25 //阻塞卡界面,是爲了併發以及順序控制 26 }
1 /// <summary> 2 /// 模擬Coding過程 3 /// </summary> 4 /// <param name="name"></param> 5 /// <param name="projectName"></param> 6 private static string Coding(string name, string projectName) 7 { 8 Console.WriteLine($"****************Coding Start {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); 9 long lResult = 0; 10 for (int i = 0; i < 1_000_000_000; i++) 11 { 12 lResult += i; 13 } 14 Console.WriteLine($"****************Coding End {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************"); 15 return name; 16 }
多線程異常處理多線程
1 #region 多線程異常處理 2 { 3 try 4 { 5 6 List<Task> taskList = new List<Task>(); 7 for (int i = 0; i < 100; i++) 8 { 9 string name = $"btnThreadCore_Click_{i}"; 10 taskList.Add(Task.Run(() => 11 { 12 if (name.Equals("btnThreadCore_Click_11")) 13 { 14 throw new Exception("btnThreadCore_Click_11異常"); 15 } 16 else if (name.Equals("btnThreadCore_Click_12")) 17 { 18 throw new Exception("btnThreadCore_Click_12異常"); 19 } 20 else if (name.Equals("btnThreadCore_Click_38")) 21 { 22 throw new Exception("btnThreadCore_Click_38異常"); 23 } 24 Console.WriteLine($"This is {name}成功 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); 25 })); 26 } 27 //多線程裏面拋出的異常,會終結當前線程;可是不會影響別的線程; 28 //那線程異常哪裏去了? 被吞了, 29 //假如我想獲取異常信息,還須要通知別的線程 30 Task.WaitAll(taskList.ToArray());//1 能夠捕獲到線程的異常 31 } 32 catch (AggregateException aex)//2 須要try-catch-AggregateException 33 { 34 foreach (var exception in aex.InnerExceptions) 35 { 36 Console.WriteLine(exception.Message); 37 } 38 } 39 catch (Exception ex)//能夠多catch 先具體再所有 40 { 41 Console.WriteLine(ex); 42 } 43 //線程異常後常常是須要通知別的線程,而不是等到WaitAll,問題就是要線程取消 44 //工做中常規建議:多線程的委託裏面不容許異常,包一層try-catch,而後記錄下來異常信息,完成須要的操做 45 } 46 #endregion
多線程裏面拋出的異常,會終結當前線程;可是不會影響別的線程;線程異常哪裏去了? 被吞了併發
多線程的委託裏面不容許異常,包一層try-catch,而後記錄下來異常信息 ,通知別的線程async
線程取消分佈式
1 { 2 CancellationTokenSource cts = new CancellationTokenSource(); 3 var token = cts.Token; cts.Cancel(); 4 CancellationTokenSource cts2 = new CancellationTokenSource(); 5 var token2 = cts2.Token; 6 List<Task> taskList = new List<Task>(); 7 for (int i = 0; i < 10; i++) 8 { 9 int k = i; 10 switch (i%5) 11 { 12 case 0: 13 taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=0"); }));break; 14 case 1: 15 taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=1"); },token)); break; 16 case 2: 17 taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=2"); }, token2)); break; 18 case 3: 19 taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=3"); })); break; 20 case 4: 21 taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=4"); 22 throw new Exception("throw new Exception"); 23 })); break; 24 } 25 } 26 //Thread.Sleep(500); 27 cts2.Cancel(); 28 try 29 { 30 Task.WaitAll(taskList.ToArray()); 31 }catch(AggregateException ae) 32 { 33 foreach (var item in ae.InnerExceptions) 34 { 35 Console.WriteLine($"{item.GetType().Name}:{item.Message}"); 36 } 37 } 38 Console.WriteLine("**********************************"); 39 foreach (var item in taskList) 40 { 41 Console.WriteLine($"Id:{item.Id},Status:{item.Status}"); 42 if (item.Exception != null) 43 { 44 foreach (var ex in item.Exception.InnerExceptions) 45 { 46 Console.WriteLine($"{ex.GetType().Name}:{ex.Message}"); 47 } 48 } 49 } 50 }
運行上面的代碼,有四個任務被取消,取消註釋,則有兩個任務被取消ide
線程安全
若是你的代碼在進程中有多個線程同時運行這一段,若是每次運行的結果都跟單線程運行時的結果一致,那麼就是線程安全的
線程安全問題通常都是有全局變量/共享變量/靜態變量/硬盤文件/數據庫的值,只要多線程都能訪問和修改
Lock
一、Lock解決多線程衝突
Lock是語法糖,Monitor.Enter,佔據一個引用,別的線程就只能等着
推薦鎖是private static readonly object
A 不能是Null,能夠編譯不能運行;
B 不推薦lock(this),外面若是也要用實例,就衝突了
1 public class LockHelper 2 { 3 public void Show() 4 { 5 LockTest test = new LockTest(); 6 Console.WriteLine(DateTime.Now); 7 Task.Delay(10).ContinueWith(t => 8 { 9 lock (test) 10 { 11 Console.WriteLine($"*********Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********"); 12 Thread.Sleep(5000); 13 Console.WriteLine($"*********End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********"); 14 } 15 }); 16 test.LockThis(); 17 } 18 } 19 public class LockTest 20 { 21 private int lockthis; 22 public void LockThis() 23 { 24 lock (this) 25 //遞歸調用,lock this 會不會死鎖? 不會死鎖! 26 //這裏是同一個線程,這個引用就是被這個線程所佔據 27 { 28 Thread.Sleep(1000); 29 this.lockthis++; 30 if (this.lockthis < 10) 31 this.LockThis(); 32 else 33 Console.WriteLine($"This is {nameof(LockThis)}:{this.lockthis} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); 34 } 35 } 36 }
這裏LockThis自身遞歸調用不會死鎖,這個引用被當前線程佔用,但當另外的實例要使用時就衝突了,必須等待LockThis執行完成後,釋放當前實例,外面的實例才能被調用
C 不該該是string; string在內存分配上是重用的,會衝突
1 { 2 LockTest test = new LockTest(); 3 Console.WriteLine(DateTime.Now); 4 string lockString = "lockString"; 5 Task.Delay(1000).ContinueWith(t => 6 { 7 lock (lockString) 8 { 9 Console.WriteLine($"****lockString Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********"); 10 Thread.Sleep(5000); 11 Console.WriteLine($"****lockString End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********"); 12 } 13 }); 14 test.LockString(); 15 } 16 public class LockTest 17 { 18 private int lockthis; 19 public void LockThis() 20 { 21 lock (this) 22 //遞歸調用,lock this 會不會死鎖? 不會死鎖! 23 //這裏是同一個線程,這個引用就是被這個線程所佔據 24 { 25 Thread.Sleep(1000); 26 this.lockthis++; 27 if (this.lockthis < 10) 28 this.LockThis(); 29 else 30 Console.WriteLine($"This is {nameof(LockThis)}:{this.lockthis} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); 31 } 32 } 33 34 private string lockString= "lockString"; 35 public void LockString() 36 { 37 lock (lockString) 38 { 39 Thread.Sleep(2000); 40 Console.WriteLine($"This is {nameof(LockString)}:{this.lockString} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); 41 Thread.Sleep(2000); 42 } 43 } 44 }
String類型在內存分配上按享元模式設計的,某個字符串被佔用,其餘線程就必須等待字符串釋放後才能使用
D Lock裏面的代碼不要太多,這裏是單線程的
二、線程安全集合
System.Collections.Concurrent.ConcurrentQueue<T>
三、 數據分拆,避免多線程操做同一個數據;又安全又高效
1 private int _sync = 0; 2 private int _async = 0; 3 private List<int> listInt = new List<int>(); 4 private static readonly object lockObject = new object(); 5 public void LockObject() 6 { 7 for (int i = 0; i < 1000; i++) 8 { 9 this._sync++; 10 } 11 for (int i = 0; i < 1000; i++) 12 { 13 Task.Run(() => this._async++); 14 } 15 for (int i = 0; i < 1000; i++) 16 { 17 int k = i; 18 Task.Run(() => this.listInt.Add(k)); 19 } 20 Thread.Sleep(5 * 1000); 21 Console.WriteLine($"_sync={this._sync} _async={this._async} listInt={this.listInt.Count}"); 22 }
運行上面的代碼發現_sync=1000 _async與listInt集合個數都少於1000
1 public void LockObject() 2 { 3 for (int i = 0; i < 1000; i++) 4 { 5 this._sync++; 6 } 7 for (int i = 0; i < 1000; i++) 8 { 9 Task.Run(() => { 10 lock (lockObject) 11 { 12 this._async++; 13 } 14 }); 15 } 16 for (int i = 0; i < 1000; i++) 17 { 18 int k = i; 19 Task.Run(() => { 20 lock (lockObject) 21 { 22 this.listInt.Add(k); 23 } 24 }); 25 } 26 Thread.Sleep(5 * 1000); 27 Console.WriteLine($"_sync={this._sync} _async={this._async} listInt={this.listInt.Count}"); 28 }
使用lock包裝後 _async與listInt集合個數都爲1000, 使用lock後 只有一個線程才能進入lock方法塊內,至關於把程序又變回了單線程
微軟文檔:
lock:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/lock-statement
CancellationTokenSource:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.cancellationtokensource?view=netframework-4.8
CancellationToken:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.cancellationtoken?view=netframework-4.8