在項目中如果是web請求時候,IIS會自動分配一個線程來進行處理,如果很多個應用程序共享公用一個IIS的時候,線程分配可能會出現一個問題(當然也是我的需求造成的)
之前在做項目的時候,有一個需求,就是當程序啟動的時候,希望能夠啟動一定數目的線程,然后每一個線程始終都是在運行的狀態,不進行釋放,然后循環去做一些事情。那么IIS的線程管理可能就不是我想要的,因為我想我的一些程序,只用我開啟的線程來做工作。也就是說我想模擬一個線程池,每次有一個調用的時候從自定義線程池中取出一個,用完再放回去。
談談我的思路:
1.程序一啟動就通過for循環來創建,一定數目的線程(這個數目是可以配置的)
2.至少要有三個容器來存儲線程,分別是工作線程隊列和空閑線程隊列以及等待隊列
3.使用線程中的AutoResetEvent類,初始每一個線程都是unsignaled狀態,線程一啟動就一直在循環調用WaitOne()方法,那么每次外部調用的時候,都調用一次這個類實例對象的set,線程然后就可以繼續做下面的工作了。
4.至少兩個方法:
第一個開放給外部,讓外部的方法能夠被傳入執行,然后這個方法能夠判斷空閑隊列,等待隊列,以及工作隊列的狀態,如果傳入的時候發現,空閑隊列有空閑的線程就直接,將任務委托給空閑隊列的一個線程執行,否則把它放到等待隊列。
第二個方法,需要能夠將工作完成的線程從工作隊列移動到空閑隊列,然后判斷一下等待隊列是不是有任務,有的話就交給空閑隊列里面的線程來執行。
體思路如上,可以試試先寫一下。
1.因為每個線程都有一個AutoResetEvent的實例,所以最好把Thread進行封裝,變成我們自己的Thread。
1 public class Task 2 { 3 #region Variable 4 //一個AutoResetEvent實例 5 private AutoResetEvent _locks = new AutoResetEvent(false); 6 //一個Thread實例 7 private Thread _thread; 8 // 綁定回調方法,就是外部實際執行的任務 9 public Action _taskAction; 10 11 //定義一個事件用來綁定工作完成后的操作,也就是4中所說的工作隊列向空閑隊列移動 12 public event Action<Task> WorkComplete; 13 14 /// <summary> 15 ///設置線程擁有的Key 16 /// </summary> 17 public string Key { get; set; } 18 19 #endregion 20 21 //線程需要做的工作 22 private void Work() 23 { 24 while (true) 25 { 26 //判斷信號狀態,如果有set那么 _locks.WaitOne()后的程序就繼續執行 27 _locks.WaitOne(); 28 _taskAction(); 29 //執行事件 30 WorkComplete(this); 31 } 32 } 33 34 #region event 35 //構造函數 36 public Task() 37 { 38 //スレッドオブジェクトを初期化する 39 _thread = new Thread(Work); 40 _thread.IsBackground = true; 41 Key = Guid.NewGuid().ToString(); 42 //線程開始執行 43 _thread.Start(); 44 } 45 46 //Set開起信號 47 public void Active() 48 { 49 _locks.Set(); 50 } 51 52 #endregion 53 }
解釋:上面那個Key的作用,因為多個線程同時進行的時候,我們並不知道哪一個線程的工作先執行完,所以說上面的工作隊列,實際上應該用一個字典來保存,這樣我們就能在一個線程結束工作之后,通過這 里的KEY(每個線程不一樣),來進行定位了。
2.線程封裝好了,然后就可以實現線程池了
1 public class TaskPool 2 { 3 4 #region Variable 5 //創建的線程數 6 private int _threadCount; 7 //空閑線程隊列 8 private Queue<Task> _freeQueue; 9 //工作線程字典(為什么?) 10 private Dictionary<string, Task> _workingDictionary; 11 //空閑隊列,存放需要被執行的外部函數 12 private Queue<Action> _waitQueue; 13 #endregion 14 15 #region Event 16 //自定義線程池的構造函數 17 public TaskPool() 18 { 19 _workingDictionary = new Dictionary<string, Task>(); 20 _freeQueue = new Queue<Task>(); 21 _waitQueue = new Queue<Action>(); 22 _threadCount = 10; 23 24 Task task = null; 25 //產生固定數目的線程 26 for (int i = 0; i < _threadCount; i++) 27 { 28 task = new Task(); 29 //給每一個任務綁定事件 30 task.WorkComplete += new Action<Task>(WorkComplete); 31 //將每一個新創建的線程放入空閑隊列中 32 _freeQueue.Enqueue(task); 33 } 34 } 35 36 //線程任務完成之后的工作 37 void WorkComplete(Task obj) 38 { 39 lock (this) 40 { 41 //將線程從字典中排除 42 _workingDictionary.Remove(obj.Key); 43 //將該線程放入空閑隊列 44 _freeQueue.Enqueue(obj); 45 46 //判斷是否等待隊列中有任務未完成 47 if (_waitQueue.Count > 0) 48 { 49 //取出一個任務 50 Action item = _waitQueue.Dequeue(); 51 Task newTask = null; 52 //空閑隊列中取出一個線程 53 newTask = _freeQueue.Dequeue(); 54 // 線程執行任務 55 newTask._taskAction = item; 56 //把線程放入到工作隊列當中 57 _workingDictionary.Add(newTask.Key, newTask); 58 //設置信號量 59 newTask.Active(); 60 return; 61 } 62 else 63 { 64 return; 65 } 66 } 67 } 68 69 //添加任務到線程池 70 public void AddTaskItem(Action taskItem) 71 { 72 lock (this) 73 { 74 Task task = null; 75 //判斷空閑隊列是否存在線程 76 if (_freeQueue.Count > 0) 77 { 78 //存在線程,取出一個線程 79 task = _freeQueue.Dequeue(); 80 //將該線程放入工作隊列 81 _workingDictionary.Add(task.Key, task); 82 //執行傳入的任務 83 task._taskAction = taskItem; 84 //設置信號量 85 task.Active(); 86 return; 87 } 88 else 89 { 90 //空閑隊列中沒有空閑線程,就把任務放到等待隊列中 91 _waitQueue.Enqueue(taskItem); 92 return; 93 } 94 } 95 } 96 #endregion 97 98 }
解釋:這里的兩個方法,基本符合我的設想,注意每一個方法里面都有lock操作,這就保證了,多個線程進行操作相同的隊列對象的時候,能夠進行互斥。保證一個時間只有一個線程在操作。
測試代碼:
class Program { static void Main(string[] args) { TaskPool _taskPool = new TaskPool(); Console.WriteLine(Thread.CurrentThread.ManagedThreadId); for (var i = 0; i < 20; i++) { _taskPool.AddTaskItem(Print); } Console.Read(); } public static void Print() { Console.WriteLine("Do Something!"); } } }
這里我執行了20次print操作,看看結果是啥:
從圖中看到20次確實執行了,但是看不到線程是哪些,稍微修改一下自定義的線程池。
1.在自定義線程的構造函數中添加:如下代碼,查看哪些線程被創建了
1 public Task() 2 { 3 _thread = new Thread(Work); 4 _thread.IsBackground = true; 5 Key = Guid.NewGuid().ToString(); 6 //線程開始執行 7 _thread.Start(); 8 Console.WriteLine("Thread:"+_thread.ManagedThreadId+" has been created!"); 9 }
2.在線程完成工作方法之后添加如下代碼,查看哪些線程參與執行任務
1 private void Work() 2 { 3 while (true) 4 { 5 //判斷信號狀態,如果有set那么 _locks.WaitOne()后的程序就繼續執行 6 _locks.WaitOne(); 7 _taskAction(); 8 Console.WriteLine("Thread:" + Thread.CurrentThread.ManagedThreadId+"workComplete"); 9 //執行事件 10 WorkComplete(this); 11 } 12 }
3.修改客戶端程序
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 TaskPool _taskPool = new TaskPool(); 6 7 for (var i = 0; i < 20; i++) 8 { 9 _taskPool.AddTaskItem(Print); 10 } 11 Console.Read(); 12 } 13 14 public static void Print() 15 { 16 Thread.Sleep(10000); 17 } 18 19 }
測試結果:
從結果可以看到,開始和執行的線程都是固定的那10個,所以這個程序是可用的。