老趙在前幾次的POST裡分析了.NET的內建線程池,由於.NET內建的線程池在底層通過win32api調用的windows的進程附帶的線程池,所以對於進程,這個線程池是唯一的,而且很不幸的是很多.NET自身的操作也需要通過這個線程池來完成,比如timmer。所以我們來嘗試自己寫一個線程池,這個線程池不是靜態,一個進程裡可以出現多個線程池的執行個體,我們可以隨時放入要執行的操作,由於沒有系統線程池的建立線程的頻率的限制,對於大量突發線程的頻繁操作來說自訂的線程池會比較好用。
首先我們來分析一下實現的原理。線程池,顧名思義就是在一個“池”中儲存了一組可以重複利用的線程對象,從而可以節省建立線程的開銷。那麼首要需要解決的問題就是複用線程了。在.NET中我們建立一個線程的方式可以是:
Thread T = new Thread(Method);
我們可以把線程看作是方法的一個封裝,那麼我們要複用線程就需要能夠動態改變線程體的方法。為了達到這個目的,我們需要把具體要執行的方法封裝一下,這裡我們就通過delegate來封裝,然後用另外一個方法來代替被封裝的方法稱為線程體。如下:
static Thread T = new Thread(TbodyWrapper); static object context; static WaitCallback TbodyInstance = new WaitCallback(Tbody); static void TbodyWrapper() { while (true) { //TO DO 阻塞線程使其掛起 TbodyInstance(context); } } static void Tbody(object arg) { //TO DO 真正的線程體 }
如此這般,當線程被掛起的時候,我們就能夠修改TbodyInstance,那麼就等於修改了線程體要執行的代碼,而線程被掛起之後就相當於是閑置起來了。
接下來要解決的問題就是如何阻塞線程,首先我們把上面的代碼放到一個類中,我們稱這個類為線程封裝器類,在這個類中封裝了一個Thread對象,而線程體的wapper方法就是這個類中的一個方法,我們在類中添加一個AutoResetEvent的對象,通過它我們就可以線上程體的封裝方法一進入和每次執行完一次迴圈就阻塞,在另一個方法中set就可以喚醒線程,如下:
class Task : IDisposable { private AutoResetEvent locks; private Thread T; public WaitCallback taskWorkItem;//真實要執行的線程體 private bool working; public object contextdata { get; set; } public event Action<Task> WorkComplete; //當執行完成後通知線程池執行後續操作的事件 public Task() { locks = new AutoResetEvent(false); T = new Thread(Work); T.IsBackground = true; working = true; contextdata = new object(); T.Start(); } public void Active() { locks.Set(); } public void SetWorkItem(WaitCallback action, object context) { taskWorkItem = action; contextdata = context; } private void Work() { while (working) { locks.WaitOne(); taskWorkItem(contextdata); WorkComplete(this); } } public void Close() { working = false; } public void Dispose() { //throw new NotImplementedException(); try { T.Abort(); } catch { } } }
如此這般,當線程封裝器初始化的時候,線程就啟動並被阻塞掛起,這個時候我們可以設定線程執行的方法體委派,並且指定傳遞給線程的參數.當執行Active方法後線程被喚醒,並開始執行線程體,當線程體執行完之後會開始新迴圈並被繼續掛起,如此這般周而復始。由此我們完成了對線程對象的服用。
----------------------我是分割線------------------------
接下來我們需要一個容器來儲存和管理這些可複用的線程封裝器,這個容器也就是所謂的線程池了。為了方便描述,以下我們簡稱線程封裝器類為線程。
我們首先需要一個對象來儲存所有已經建立出來的線程。為了同時方便定位特定的線程,我們給每個線程增加一個ID的屬性,在建立的時候用GUID來賦值,這樣我們就能用Dictionary<K,V>來儲存所有已經建立出來的線程引用。
為了減少遍曆,我們將正在工作的線程也放在一個dictionary中,最後把所有閒置線程放在一個Queue裡頭。
由於不能無限量的增加線程,所以設定了最大線程數的限制,所以如果當需要執行的線程超過的時候為了不拋出異常,我們需要用一個結構來吧要執行的操作和資料排入佇列,在有空閑線程的時候好取出來繼續執行。
所以我們需要重點實現的就是兩個過程,一個是加入線程,一個是當線程執行完畢後所執行的操作。
最後我們來看看完整實現的代碼:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;namespace TaskPoolTest{ public class TaskPool : IDisposable { private int max = 25; //最大線程數 private int min = 4; //最小線程數 private int increment = 2; //當活動線程不足的時候新增線程的增量 private Dictionary<string, Task> publicpool; //所有的線程 private Queue<Task> freequeue; //空閑線程隊列 private Dictionary<string, Task> working; //正在工作的線程 private Queue<Waititem> waitlist; //等待執行工作隊列 //設定最大線程數 public void Setmaxthread(int Value) { lock (this) { max = Value; } } //設定最小線程數 public void Setminthread(int Value) { lock (this) { min = Value; } } //設定增量 public void Setincrement(int Value) { lock (this) { increment = Value; } } //初始化線程池 public TaskPool() { publicpool = new Dictionary<string, Task>(); working = new Dictionary<string, Task>(); freequeue = new Queue<Task>(); waitlist = new Queue<Waititem>(); Task t = null;//先建立最小線程數的線程 for (int i = 0; i < min; i++) { t = new Task();//註冊線程完成時觸發的事件 t.WorkComplete += new Action<Task>(t_WorkComplete);//加入到所有線程的字典中 publicpool.Add(t.Key, t);//因為還沒加入具體的工作委託就先放入空閑隊列 freequeue.Enqueue(t); } } //線程執行完畢後的觸發事件 void t_WorkComplete(Task obj) { lock (this) { //首先因為工作執行完了,所以從正在工作字典裡刪除 working.Remove(obj.Key);//檢查是否有等待執行的操作,如果有等待的優先執行等待的任務 if (waitlist.Count > 0) {//先要登出當前的線程,將其從線程字典刪除 publicpool.Remove(obj.Key); obj.Close();//從等待任務隊列提取一個任務 Waititem item = waitlist.Dequeue(); Task nt = null;//如果有閒置線程,就是用閒置線程來處理 if (freequeue.Count > 0) { nt = freequeue.Dequeue(); } else {//如果沒有閒置線程就再新建立一個線程來執行 nt = new Task(); publicpool.Add(nt.Key, nt); nt.WorkComplete += new Action<Task>(t_WorkComplete); }//設定線程的執行委派物件和內容物件 nt.taskWorkItem = item.Works; nt.contextdata = item.Context;//添加到工作字典中 working.Add(nt.Key, nt);//喚醒線程開始執行 nt.Active(); } else {//如果沒有等待執行的操作就回收多餘的背景工作執行緒 if (freequeue.Count > min) {//當空閑線程超過最小線程數就回收多餘的這一個 publicpool.Remove(obj.Key); obj.Close(); } else {//如果沒超過就把線程從工作字典放入空閑隊列 obj.contextdata = null; freequeue.Enqueue(obj); } } } } //添加工作委託的方法 public void AddTaskItem(WaitCallback TaskItem, object Context) { lock (this) { Task t = null; int len = publicpool.Values.Count;//如果線程沒有到達最大值 if (len < max) {//如果空閑列表非空 if (freequeue.Count > 0) {//從空閑隊列pop一個線程 t = freequeue.Dequeue();//加入工作字典 working.Add(t.Key, t);//設定執行委託 t.taskWorkItem = TaskItem;//設定狀態物件 t.contextdata = Context;//喚醒線程開始執行 t.Active(); return; } else {//如果沒有空閑隊列了,就根據增量建立線程 for (int i = 0; i < increment; i++) { //判斷線程的總量不能超過max if ((len + i) <= max) { t = new Task();//設定完成響應事件 t.WorkComplete += new Action<Task>(t_WorkComplete);//加入線程字典 publicpool.Add(t.Key, t);//加入空閑隊列 freequeue.Enqueue(t); } else { break; } }//從空閑隊列提出出來設定後開始執行 t = freequeue.Dequeue(); working.Add(t.Key, t); t.taskWorkItem = TaskItem; t.contextdata = Context; t.Active(); return; } } else {//如果線程達到max就把任務加入等待隊列 waitlist.Enqueue(new Waititem() { Context = Context, Works = TaskItem }); } } } //回收資源 public void Dispose() { //throw new NotImplementedException(); foreach (Task t in publicpool.Values) {//關閉所有的線程 using (t) { t.Close(); } } publicpool.Clear(); working.Clear(); waitlist.Clear(); freequeue.Clear(); }//儲存等待隊列的類 class Waititem { public WaitCallback Works { get; set; } public object Context { get; set; } } }//線程封裝器類 class Task : IDisposable { private AutoResetEvent locks; //線程鎖 private Thread T; //線程對象 public WaitCallback taskWorkItem; //線程體委託 private bool working; //線程是否工作 public object contextdata { get; set; } public event Action<Task> WorkComplete; //線程完成一次操作的事件//用於字典的Key public string Key { get; set; } //初始化封裝器 public Task() {//設定線程一進入就阻塞 locks = new AutoResetEvent(false); Key = Guid.NewGuid().ToString(); //初始化線程對象 T = new Thread(Work); T.IsBackground = true; working = true; contextdata = new object();//開啟線程 T.Start(); } //喚醒線程 public void Active() { locks.Set(); } //設定執行委託和狀態物件 public void SetWorkItem(WaitCallback action, object context) { taskWorkItem = action; contextdata = context; } //線程體封裝方法 private void Work() { while (working) {//阻塞線程 locks.WaitOne(); taskWorkItem(contextdata);//完成一次執行,觸發事件 WorkComplete(this); } } //關閉線程 public void Close() { working = false; }//回收資源 public void Dispose() { //throw new NotImplementedException(); try { T.Abort(); } catch { } } }}
最後看看如何使用這個線程池類:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;namespace TaskPoolTest{ class Program { static void Main(string[] args) { TaskPool pool = new TaskPool(); for (int i = 0; i < 50; i++) { pool.AddTaskItem( x => { for (int j = 0; j < 5; j++) { Thread.Sleep(10); Console.WriteLine("Thread " + (int)x + " print " + j); } }, i); } Console.ReadKey(); } }}
執行的結果