一個自定義線程池的小Demo


在項目中如果是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個,所以這個程序是可用的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM