最近自己寫了個小爬蟲,里面用到了多線程技術,忽然發現對此技術竟然有些陌生了,於是乎開始瘋狂的去問度娘,在此記錄下來,以便自己和各位小伙伴們學習。
一、什么是線程
一個應用程序就相當於一個進程,進程擁有應用程序的所有資源進程包括線程,進程的資源被線程共享,但不擁有線程。我們可以打開電腦中的任務管理器,運行的.exe都是一個進程,里面的分支是線程。
二、多線程
多線程其實就是進程中一段並行運行的代碼
1. 創建並啟動線程
1 static void Main() 2 { 3 //獲取線程Id 4 var threadId = Thread.CurrentThread.ManagedThreadId; 5 var thread = new Thread(Test1); 6 thread.Start(); 7 8 Console.WriteLine(threadId + "_Main()"); 9 Console.Read(); 10 } 11 12 /// <summary> 13 /// 測試方法 14 /// </summary> 15 private static void Test1() 16 { 17 //獲取線程Id 18 var threadId = Thread.CurrentThread.ManagedThreadId; 19 Console.WriteLine(threadId + "_Test()"); 20 for (int i = 0; i < 10; i++) 21 { 22 Console.WriteLine(threadId + "_" + i); 23 } 24 }
結果:
2、暫定線程諾干時間
1 static void Main() 2 { 3 //獲取線程Id 4 var threadId = Thread.CurrentThread.ManagedThreadId; 5 var thread = new Thread(Test1); 6 thread.Start(); 7 Console.WriteLine($"主線程Id{threadId}_Main()"); 8 Console.Read(); 9 } 10 11 /// <summary> 12 /// 測試方法 13 /// </summary> 14 private static void Test1() 15 { 16 //獲取線程Id 17 var threadId = Thread.CurrentThread.ManagedThreadId; 18 Console.WriteLine($"輔線程Id{threadId}_Test()"); 19 for (int i = 0; i < 10; i++) 20 { 21 Thread.Sleep(1000);//單位毫秒 22 Console.WriteLine($"輔線程Id{threadId}_{DateTime.Now}"); 23 } 24 }
結果:
3、線程合並
Thread.Join操作會阻塞當前線程,等待子線程完成后再進行運行。
1 static void Main() 2 { 3 //獲取線程Id 4 var threadId = Thread.CurrentThread.ManagedThreadId; 5 var thread = new Thread(Test1); 6 thread.Start(); 7 Console.WriteLine($"主線程Id{threadId}_Main()1"); 8 thread.Join(); 9 Console.WriteLine($"主線程Id{threadId}_Main()2"); 10 Console.Read(); 11 } 12 13 /// <summary> 14 /// 測試方法 15 /// </summary> 16 private static void Test1() 17 { 18 //獲取線程Id 19 var threadId = Thread.CurrentThread.ManagedThreadId; 20 Console.WriteLine($"輔線程Id{threadId}_Test()"); 21 for (int i = 0; i < 10; i++) 22 { 23 Thread.Sleep(1000);//單位毫秒 24 Console.WriteLine($"輔線程Id{threadId}_{DateTime.Now}"); 25 } 26 }
結果:
4、線程終止
1 static void Main() 2 { 3 //獲取線程Id 4 var threadId = Thread.CurrentThread.ManagedThreadId; 5 var thread = new Thread(Test1); 6 thread.Start(); 7 Console.WriteLine($"主線程Id{threadId}_Main()1"); 8 Thread.Sleep(3000); 9 thread.Abort(); 10 Console.WriteLine($"主線程Id{threadId}_Main()2"); 11 Console.Read(); 12 } 13 14 /// <summary> 15 /// 測試方法 16 /// </summary> 17 private static void Test1() 18 { 19 //獲取線程Id 20 var threadId = Thread.CurrentThread.ManagedThreadId; 21 Console.WriteLine($"輔線程Id{threadId}_Test()"); 22 for (int i = 0; i < 10; i++) 23 { 24 Thread.Sleep(1000);//單位毫秒 25 Console.WriteLine($"輔線程Id{threadId}_{DateTime.Now}"); 26 } 27 }
結果:
5、線程中的參數傳遞
1 static void Main() 2 { 3 //獲取線程Id 4 var threadId = Thread.CurrentThread.ManagedThreadId; 5 Console.WriteLine($"主線程Id{threadId}_Main()"); 6 //第一種參數傳遞方式 7 var thread1 = new Thread(() => Test1("小魔王")); 8 thread1.Start(); 9 10 //第二種參數傳遞方式(參數只能是一個,object類型) 11 var parameterizedThreadStart = new ParameterizedThreadStart(Test2); 12 var thread2 = new Thread(parameterizedThreadStart); 13 thread2.Start("大魔王"); 14 Console.Read(); 15 } 16 17 /// <summary> 18 /// 測試方法 19 /// </summary> 20 private static void Test1(string name) 21 { 22 //獲取線程Id 23 var threadId = Thread.CurrentThread.ManagedThreadId; 24 Console.WriteLine($"輔線程Id{threadId}_我的名字叫:{name}"); 25 } 26 27 /// <summary> 28 /// 測試方法 29 /// </summary> 30 private static void Test2(object name) 31 { 32 //獲取線程Id 33 var threadId = Thread.CurrentThread.ManagedThreadId; 34 Console.WriteLine($"輔線程Id{threadId}_我的名字叫:{name}"); 35 }
結果:
還有其他的傳遞方式,在此先不做說明了,這里只介紹Thread提供的這么幾種。
6、線程安全和線程鎖Lock
線程安全就是多線程訪問時,采用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程才可使用。線程安全情況下,不會出現數據不一致或者數據污染的問題。 線程不安全就是不提供數據訪問保護,有可能出現多個線程先后更改數據造成所得到的數據是臟數據! 若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。
lock 關鍵字通過獲取指定對象的互斥鎖,將語句塊標記為臨界區,執行語句然后釋放該鎖。
lock 確保當一個線程位於代碼的臨界區時,另一個線程不進入臨界區。如果其他線程試圖進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放。使用Lock,會導致整個應用程序串行化,降低程序的並發能力,影響性能。
到底什么場景下要使用lock保證線程安全:該串行就串行,該並行就並行。
加鎖前:
1 public static int i = 1000000; 2 static void Main() 3 { 4 //獲取線程Id 5 var threadId = Thread.CurrentThread.ManagedThreadId; 6 for (int j = 0; j < 2; j++) 7 { 8 var thread = new Thread(Test1); 9 thread.Start(); 10 } 11 Console.Read(); 12 } 13 14 /// <summary> 15 /// 測試方法 16 /// </summary> 17 private static void Test1() 18 { 19 //獲取線程Id 20 var threadId = Thread.CurrentThread.ManagedThreadId; 21 22 Console.WriteLine($"輔線程Id{threadId}_i初始值:{i}"); 23 int count = 0; 24 for (int j = 0; j < 1000000; j++) 25 { 26 i--; 27 count++; 28 } 29 Console.WriteLine($"輔線程Id{threadId}_運行次數:{count}"); 30 Console.WriteLine($"輔線程Id{threadId}_i結束值:{i}"); 31 }
結果:
加鎖后:
1 public static int i = 1000000; 2 private readonly static object objLock = new object(); 3 static void Main() 4 { 5 //獲取線程Id 6 var threadId = Thread.CurrentThread.ManagedThreadId; 7 for (int j = 0; j < 2; j++) 8 { 9 var thread = new Thread(Test1); 10 thread.Start(); 11 } 12 Console.Read(); 13 } 14 15 private static void Test1() 16 { 17 //獲取線程Id 18 var threadId = Thread.CurrentThread.ManagedThreadId; 19 20 int count = 0; 21 lock (objLock) 22 { 23 Console.WriteLine($"輔線程Id{threadId}_i初始值:{i}"); 24 for (int j = 0; j < 1000000; j++) 25 { 26 i--; 27 count++; 28 } 29 } 30 Console.WriteLine($"輔線程Id{threadId}_運行次數:{count}"); 31 Console.WriteLine($"輔線程Id{threadId}_i結束值:{i}"); 32 }
結果:
好啦,今天關於線程的知識就分箱到這里啦。