線程的基本概念
• 線程是程式執行的基本原子單位. 一個進程可以由多個線程組成.
• 每個線程都維護例外處理常式、調度優先順序和一組系統用於在調度該線程前儲存線程內容相關的結構。線程上下文包括為使線程線上程的宿主進程地址空間中無縫地繼續執行所需的所有資訊,包括線程的CPU 寄存器組和堆棧。
• 在分布式編程中,正確使用線程能夠很好的提高應用程式的效能及運行效率.實現原理是將一個進程分成多個線程,然後讓它們並發非同步執行,來提高運行效率.
• 並發執行並不是同時執行(佔有CPU),任意時刻還是只能有一個線程佔用CPU,只不過是它們爭奪CPU頻繁一些,感覺到他們似乎都在運行.
什麼時候用線程?
• 一般情況下,如果多個線程在執行時都要搶佔某一個資源或某幾個資源,則最好不用非同步線程執行.因為它們是並發執行,很可能同時爭奪某個資源有CPU,這時要麼執行資源分配演算法(比如要判斷哪個線程優先順序高,這要花費時間),或者是按時間片演算法(這樣要付出輪詢CUP/交接/讓出CPU所需的時間).
• 如果多個線程所需要的系統資源是比較均勻的,這時完全可以讓它們非同步並發執行,
使用線程的缺點
• 統將為進程和線程所需的上下文資訊使用記憶體。因此,可以建立的進程、AppDomain 對象和線程的數目會受到可用記憶體的限制。
• 跟蹤大量的線程將佔用大量的處理器時間。如果線程過多,則其中大多數線程都不會產生明顯的進度。如果大多數當前線程處於一個進程中,則其他進程中的線程的調度頻率就會很低。
• 使用許多線程式控制制代碼執行非常複雜,並可能產生許多錯誤。
• 銷毀線程需要瞭解可能發生的問題並對那些問題進行處理。
線程池
• 許多應用程式建立的線程都要在休眠狀態中消耗大量時間,以等待事件發生。這樣會浪費資源。
• 線程池通過為應用程式提供一個由系統管理的輔助線程池使您可以更為有效地使用線程。一個線程監視排到線程池的若干個等待操作的狀態。當一個等待操作完成時,線程池中的一個輔助線程就會執行對應的回呼函數。
• 實際上,如果要執行一些需要多個線程的較短任務,則使用ThreadPool 類是利用多個線程的最方便且最好的方法。使用線程池使系統能夠不僅針對此進程而且針對電腦上的其他進程(您的應用程式對其一無所知)對此情況進行最佳化以達到更好的輸送量。使用線程池使系統能夠在考慮到電腦上的所有當前進程後對線程時間片進行最佳化。
ThreadPool
• 線程池在首次建立ThreadPool 類的執行個體時被建立。線程池具有每個可用處理器25 個線程的預設限制
• 可以將與等待操作不相關的工作項目排列到線程池。若要請求由線程池中的一個線程來處理工作項目,請調用QueueUserWorkItem 方法。此方法將對將被從線程池中選定的線程調用的方法或委託的引用用作參數。一個工作項目排入隊列後就無法再取消它。
建立和終止線程
using System;using System.Threading;public class Worker{ // 啟動線程時調用此方法。 public void DoWork() { while (!_shouldStop) { Console.WriteLine("worker thread: working..."); } Console.WriteLine("worker thread: terminating gracefully."); } public void RequestStop() { _shouldStop = true; } // Volatile 用於向編譯器提示此資料 // 成員將由多個線程訪問。 private volatile bool _shouldStop;}public class WorkerThreadExample{ static void Main() { // 建立線程對象。這不會啟動該線程。 Worker workerObject = new Worker(); Thread workerThread = new Thread(workerObject.DoWork); // 啟動輔助線程。 workerThread.Start(); Console.WriteLine("main thread: Starting worker thread..."); // 迴圈直至輔助線程啟用。 while (!workerThread.IsAlive); // 為主線程設定 1 毫秒的休眠, // 以使輔助線程完成某項工作。 Thread.Sleep(1); // 請求輔助線程自行停止: workerObject.RequestStop(); // 使用 Join 方法阻塞當前線程, // 直至對象的線程終止。 workerThread.Join(); Console.WriteLine("main thread: Worker thread has terminated."); }}
使用線程池
using System;using System.Threading;// Fibonacci 類為使用輔助// 線程執行長時間的 Fibonacci(N) 計算提供了一個介面。// N 是為 Fibonacci 建構函式提供的,此外還提供了// 操作完成時對象發出的事件訊號。// 然後,可以使用 FibOfN 屬性來檢索結果。public class Fibonacci{ public Fibonacci(int n, ManualResetEvent doneEvent) { _n = n; _doneEvent = doneEvent; } // 供線程池使用的封裝方法。 public void ThreadPoolCallback(Object threadContext) { int threadIndex = (int)threadContext; Console.WriteLine("thread {0} started...", threadIndex); _fibOfN = Calculate(_n); Console.WriteLine("thread {0} result calculated...", threadIndex); _doneEvent.Set(); } // 計算第 N 個費伯納西數的遞迴方法。 public int Calculate(int n) { if (n <= 1) { return n; } else { return Calculate(n - 1) + Calculate(n - 2); } } public int N { get { return _n; } } private int _n; public int FibOfN { get { return _fibOfN; } } private int _fibOfN; ManualResetEvent _doneEvent;}public class ThreadPoolExample{ static void Main() { const int FibonacciCalculations = 10; // 每個 Fibonacci 對象使用一個事件 ManualResetEvent[] doneEvents = new ManualResetEvent[FibonacciCalculations]; Fibonacci[] fibArray = new Fibonacci[FibonacciCalculations]; Random r = new Random(); // 使用 ThreadPool 配置和啟動線程: Console.WriteLine("launching {0} tasks...", FibonacciCalculations); for (int i = 0; i < FibonacciCalculations; i++) { doneEvents[i] = new ManualResetEvent(false); Fibonacci f = new Fibonacci(r.Next(20, 40), doneEvents[i]); fibArray[i] = f; ThreadPool.QueueUserWorkItem(f.ThreadPoolCallback, i); } // 等待池中的所有線程執行計算... WaitHandle.WaitAll(doneEvents); Console.WriteLine("Calculations complete."); // 顯示結果... for (int i = 0; i < FibonacciCalculations; i++) { Fibonacci f = fibArray[i]; Console.WriteLine("Fibonacci({0}) = {1}", f.N, f.FibOfN); } }}
線程同步和互交
using System;using System.Threading;using System.Collections;using System.Collections.Generic;// 將線程同步事件封裝在此類中, // 以便於將這些事件傳遞給 Consumer 和// Producer 類。public class SyncEvents{ public SyncEvents() { // AutoResetEvent 用於“新項”事件,因為 // 我們希望每當使用者線程響應此事件時, // 此事件就會自動重設。 _newItemEvent = new AutoResetEvent(false); // ManualResetEvent 用於“退出”事件,因為 // 我們希望發出此事件的訊號時有多個線程響應。 // 如果使用 AutoResetEvent,事件 // 對象將在單個線程作出響應之後恢複為 // 未發訊號的狀態,而其他線程將 // 無法終止。 _exitThreadEvent = new ManualResetEvent(false); // 這兩個事件也放在一個 WaitHandle 數組中,以便 // 使用者線程可以使用 WaitAny 方法 // 阻塞這兩個事件。 _eventArray = new WaitHandle[2]; _eventArray[0] = _newItemEvent; _eventArray[1] = _exitThreadEvent; } // 公用屬性允許對事件進行安全訪問。 public EventWaitHandle ExitThreadEvent { get { return _exitThreadEvent; } } public EventWaitHandle NewItemEvent { get { return _newItemEvent; } } public WaitHandle[] EventArray { get { return _eventArray; } } private EventWaitHandle _newItemEvent; private EventWaitHandle _exitThreadEvent; private WaitHandle[] _eventArray;}// Producer 類(使用一個輔助線程)// 將項非同步添加到隊列中,共添加 20 個項。public class Producer { public Producer(Queue<int> q, SyncEvents e) { _queue = q; _syncEvents = e; } public void ThreadRun() { int count = 0; Random r = new Random(); while (!_syncEvents.ExitThreadEvent.WaitOne(0, false)) { lock (((ICollection)_queue).SyncRoot) { while (_queue.Count < 20) { _queue.Enqueue(r.Next(0, 100)); _syncEvents.NewItemEvent.Set(); count++; } } } Console.WriteLine("Producer thread: produced {0} items", count); } private Queue<int> _queue; private SyncEvents _syncEvents;}// Consumer 類通過自己的輔助線程使用隊列// 中的項。Producer 類使用 NewItemEvent // 將新項通知 Consumer 類。public class Consumer{ public Consumer(Queue<int> q, SyncEvents e) { _queue = q; _syncEvents = e; } public void ThreadRun() { int count = 0; while (WaitHandle.WaitAny(_syncEvents.EventArray) != 1) { lock (((ICollection)_queue).SyncRoot) { int item = _queue.Dequeue(); } count++; } Console.WriteLine("Consumer Thread: consumed {0} items", count); } private Queue<int> _queue; private SyncEvents _syncEvents;}public class ThreadSyncSample{ private static void ShowQueueContents(Queue<int> q) { // 對集合進行枚舉本來就不是安全執行緒的, // 因此在整個枚舉過程中鎖定集合以防止 // 使用者和製造者線程修改內容 // 是絕對必要的。(此方法僅由 // 主線程調用。) lock (((ICollection)q).SyncRoot) { foreach (int i in q) { Console.Write("{0} ", i); } } Console.WriteLine(); } static void Main() { // 配置結構,該結構包含線程同步 // 所需的事件資訊。 SyncEvents syncEvents = new SyncEvents(); // 泛型隊列集合用於儲存要製造和使用的 // 項。此例中使用的是“int”。 Queue<int> queue = new Queue<int>(); // 建立對象,一個用於製造項,一個用於 // 使用項。將隊列和線程同步事件傳遞給 // 這兩個對象。 Console.WriteLine("Configuring worker threads..."); Producer producer = new Producer(queue, syncEvents); Consumer consumer = new Consumer(queue, syncEvents); // 為製造者對象和使用者對象建立線程 // 對象。此步驟並不建立或啟動 // 實際線程。 Thread producerThread = new Thread(producer.ThreadRun); Thread consumerThread = new Thread(consumer.ThreadRun); // 建立和啟動兩個線程。 Console.WriteLine("Launching producer and consumer threads..."); producerThread.Start(); consumerThread.Start(); // 為製造者線程和使用者線程設定 10 秒的已耗用時間。 // 使用主線程(執行此方法的線程) // 每隔 2.5 秒顯示一次隊列內容。 for (int i = 0; i < 4; i++) { Thread.Sleep(2500); ShowQueueContents(queue); } // 向使用者線程和製造者線程發出終止訊號。 // 這兩個線程都會響應,由於 ExitThreadEvent 是 // 手動重設的事件,因此除非顯式重設,否則將保持“設定”。 Console.WriteLine("Signaling threads to terminate..."); syncEvents.ExitThreadEvent.Set(); // 使用 Join 阻塞主線程,首先阻塞到製造者線程 // 終止,然後阻塞到使用者線程終止。 Console.WriteLine("main thread waiting for threads to finish..."); producerThread.Join(); consumerThread.Join(); }}