自己动手实现自定义线程池

原文:http://www.cnblogs.com/Alexander-Lee/archive/2009/10/31/1593647.html

 

老赵在前几次的POST里分析了.NET的自带线程池,由于.NET自带的线程池在底层通过win32api调用的windows的进程附带的线程池,所以对于进程,这个线程池是唯一的,而且很不幸的是很多.NET自身的操作也需要通过这个线程池来完成,比如timmer。所以我们来尝试自己写一个线程池,这个线程池不是静态的,一个进程里可以出现多个线程池的实例,我们可以随时放入要执行的操作,由于没有系统线程池的创建线程的频率的限制,对于大量突发线程的频繁操作来说自定义的线程池会比较好用。

首先我们来分析一下实现的原理。线程池,顾名思义就是在一个“池”中保存了一组可以重复利用的线程对象,从而可以节省创建线程的开销。那么首要需要解决的问题就是复用线程了。在.NET中我们创建一个线程的方式可以是:

Thread T = new Thread(Method);

 

我们可以把线程看作是方法的一个包装,那么我们要复用线程就需要能够动态的改变线程体的方法。为了达到这个目的,我们需要把具体要执行的方法包装一下,这里我们就通过delegate来包装,然后用另外一个方法来代替被包装的方法称为线程体。如下:

 

01 static Thread T = new Thread(TbodyWrapper);
02 static object context;
03 static WaitCallback TbodyInstance = new WaitCallback(Tbody);
04 static void TbodyWrapper()
05 {
06     while (true)
07     {
08         //TO DO 阻塞线程使其挂起
09         TbodyInstance(context);
10     }
11 }
12 static void Tbody(object arg)
13 {
14     //TO DO 真正的线程体
15 }

如此这般,当线程被挂起的时候,我们就能够修改TbodyInstance,那么就等于修改了线程体要执行的代码,而线程被挂起之后就相当于是闲置起来了。

接下来要解决的问题就是如何阻塞线程,首先我们把上面的代码放到一个类中,我们称这个类为线程包装器类,在这个类中包装了一个Thread对象,而线程体的wapper方法就是这个类中的一个方法,我们在类中添加一个AutoResetEvent的对象,通过它我们就可以在线程体的包装方法一进入和每次执行完一次循环就阻塞,在另一个方法中set就可以唤醒线程,如下:

01 class Task : IDisposable
02 {
03     private AutoResetEvent locks;
04     private Thread T;
05     public WaitCallback taskWorkItem;//真实要执行的线程体
06     private bool working;
07     public object contextdata
08     {
09         get;
10         set;
11     }
12     public event Action<Task> WorkComplete; //当执行完成后通知线程池执行后续操作的事件
13     public Task()
14     {
15         locks = new AutoResetEvent(false);
16         T = new Thread(Work);
17         T.IsBackground = true;
18         working = true;
19         contextdata = new object();
20         T.Start();
21     }
22     public void Active()
23     {
24         locks.Set();
25     }
26     public void SetWorkItem(WaitCallback action, object context)
27     {
28         taskWorkItem = action;
29         contextdata = context;
30     }
31     private void Work()
32     {
33         while (working)
34         {
35             locks.WaitOne();
36             taskWorkItem(contextdata);
37             WorkComplete(this);
38         }
39     }
40     public void Close()
41     {
42         working = false;
43     }
44     public void Dispose()
45     {
46         //throw new NotImplementedException();
47         try
48         {
49             T.Abort();
50         }
51         catch { }
52     }
53 }

如此这般,当线程包装器初始化的时候,线程就启动并被阻塞挂起,这个时候我们可以设置线程执行的方法体委派,并且指定传递给线程的参数.当执行Active方法后线程被唤醒,并开始执行线程体,当线程体执行完之后会开始新循环并被继续挂起,如此这般周而复始。由此我们完成了对线程对象的服用。

----------------------我是分割线------------------------

接下来我们需要一个容器来保存和管理这些可复用的线程包装器,这个容器也就是所谓的线程池了。为了方便描述,以下我们简称线程包装器类为线程。

我们首先需要一个对象来保存所有已经创建出来的线程。为了同时方便定位特定的线程,我们给每个线程增加一个ID的属性,在创建的时候用GUID来赋值,这样我们就能用Dictionary<K,V>来存储所有已经创建出来的线程引用。

为了减少遍历,我们将正在工作的线程也放在一个dictionary中,最后把所有空闲的线程放在一个Queue里头。

由于不能无限量的增加线程,所以设置了最大线程数的限制,所以如果当需要执行的线程超过的时候为了不抛出异常,我们需要用一个结构来吧要执行的操作和数据加入队列,在有空闲线程的时候好取出来继续执行。

所以我们需要重点实现的就是两个过程,一个是加入线程,一个是当线程执行完毕后所执行的操作。

最后我们来看看完整实现的代码:

001 using System;
002 using System.Collections.Generic;
003 using System.Linq;
004 using System.Text;
005 using System.Threading;
006   
007 namespace TaskPoolTest
008 {
009     public class TaskPool : IDisposable
010     {
011   
012         private int max = 25; //最大线程数
013         private int min = 4;  //最小线程数
014         private int increment = 2; //当活动线程不足的时候新增线程的增量
015   
016         private Dictionary<string, Task> publicpool; //所有的线程
017         private Queue<Task> freequeue;  //空闲线程队列
018         private Dictionary<string, Task> working;   //正在工作的线程
019         private Queue<Waititem> waitlist;  //等待执行工作队列
020   
021         //设置最大线程数
022         public void Setmaxthread(int Value)
023         {
024             lock (this)
025             {
026                 max = Value;
027             }
028         }
029         //设置最小线程数
030         public void Setminthread(int Value)
031         {
032             lock (this)
033             {
034                 min = Value;
035             }
036         }
037         //设置增量
038         public void Setincrement(int Value)
039         {
040             lock (this)
041             {
042                 increment = Value;
043             }
044         }
045         //初始化线程池
046         public TaskPool()
047         {
048             publicpool = new Dictionary<string, Task>();
049             working = new Dictionary<string, Task>();
050             freequeue = new Queue<Task>();
051             waitlist = new Queue<Waititem>();
052             Task t = null;
053             //先创建最小线程数的线程
054             for (int i = 0; i < min; i++)
055             {
056                 t = new Task();
057                 //注册线程完成时触发的事件
058                 t.WorkComplete += new Action<Task>(t_WorkComplete);
059                 //加入到所有线程的字典中
060                 publicpool.Add(t.Key, t);
061                 //因为还没加入具体的工作委托就先放入空闲队列
062                 freequeue.Enqueue(t);
063             }
064   
065         }
066         //线程执行完毕后的触发事件
067         void t_WorkComplete(Task obj)
068         {
069             lock (this)
070             {
071                 //首先因为工作执行完了,所以从正在工作字典里删除
072                 working.Remove(obj.Key);
073                 //检查是否有等待执行的操作,如果有等待的优先执行等待的任务
074                 if (waitlist.Count > 0)
075                 {
076                     //先要注销当前的线程,将其从线程字典删除
077                     publicpool.Remove(obj.Key);
078                     obj.Close();
079                     //从等待任务队列提取一个任务
080                     Waititem item = waitlist.Dequeue();
081                     Task nt = null;
082                     //如果有空闲的线程,就是用空闲的线程来处理
083                     if (freequeue.Count > 0)
084                     {
085                         nt = freequeue.Dequeue();
086                     }
087                     else
088                     {
089                         //如果没有空闲的线程就再新创建一个线程来执行
090                         nt = new Task();
091                         publicpool.Add(nt.Key, nt);
092                         nt.WorkComplete += new Action<Task>(t_WorkComplete);
093                     }
094                     //设置线程的执行委托对象和上下文对象
095                     nt.taskWorkItem = item.Works;
096                     nt.contextdata = item.Context;
097                     //添加到工作字典中
098                     working.Add(nt.Key, nt);
099                     //唤醒线程开始执行
100                     nt.Active();
101                 }
102                 else
103                 {
104                     //如果没有等待执行的操作就回收多余的工作线程
105                     if (freequeue.Count > min)
106                     {
107                         //当空闲线程超过最小线程数就回收多余的这一个
108                         publicpool.Remove(obj.Key);
109                         obj.Close();
110                     }
111                     else
112                     {
113                         //如果没超过就把线程从工作字典放入空闲队列
114                         obj.contextdata = null;
115                         freequeue.Enqueue(obj);
116                     }
117                 }
118             }
119         }
120         //添加工作委托的方法
121         public void AddTaskItem(WaitCallback TaskItem, object Context)
122         {
123             lock (this)
124             {
125                 Task t = null;
126                 int len = publicpool.Values.Count;
127                 //如果线程没有到达最大值
128                 if (len < max)
129                 {
130                     //如果空闲列表非空
131                     if (freequeue.Count > 0)
132                     {
133                         //从空闲队列pop一个线程
134                         t = freequeue.Dequeue();
135                         //加入工作字典
136                         working.Add(t.Key, t);
137                         //设置执行委托
138                         t.taskWorkItem = TaskItem;
139                         //设置状态对象
140                         t.contextdata = Context;
141                         //唤醒线程开始执行
142                         t.Active();
143                         return;
144                     }
145                     else
146                     {
147                         //如果没有空闲队列了,就根据增量创建线程
148                         for (int i = 0; i < increment; i++)
149                         {
150                             //判断线程的总量不能超过max
151                             if ((len + i) <= max)
152                             {
153                                 t = new Task();
154                                 //设置完成响应事件
155                                 t.WorkComplete += new Action<Task>(t_WorkComplete);
156                                 //加入线程字典
157                                 publicpool.Add(t.Key, t);
158                                 //加入空闲队列
159                                 freequeue.Enqueue(t);
160                             }
161                             else
162                             {
163                                 break;
164                             }
165                         }
166                         //从空闲队列提出出来设置后开始执行
167                         t = freequeue.Dequeue();
168                         working.Add(t.Key, t);
169                         t.taskWorkItem = TaskItem;
170                         t.contextdata = Context;
171                         t.Active();
172                         return;
173                     }
174                 }
175                 else
176                 {
177                     //如果线程达到max就把任务加入等待队列
178                     waitlist.Enqueue(new Waititem() { Context = Context, Works = TaskItem });
179                 }
180             }
181         }
182   
183         //回收资源
184         public void Dispose()
185         {
186             //throw new NotImplementedException();
187             foreach (Task t in publicpool.Values)
188             {
189                 //关闭所有的线程
190                 using (t) { t.Close(); }
191             }
192             publicpool.Clear();
193             working.Clear();
194             waitlist.Clear();
195             freequeue.Clear();
196         }
197         //存储等待队列的类
198         class Waititem
199         {
200             public WaitCallback Works { get; set; }
201             public object Context { get; set; }
202         }
203     }
204     //线程包装器类
205     class Task : IDisposable
206     {
207         private AutoResetEvent locks; //线程锁
208         private Thread T;  //线程对象
209         public WaitCallback taskWorkItem; //线程体委托
210         private bool working;  //线程是否工作
211         public object contextdata
212         {
213             get;
214             set;
215         }
216         public event Action<Task> WorkComplete;  //线程完成一次操作的事件
217         //用于字典的Key
218         public string Key
219         {
220             get;
221             set;
222         }
223         //初始化包装器
224         public Task()
225         {
226             //设置线程一进入就阻塞
227             locks = new AutoResetEvent(false);
228             Key = Guid.NewGuid().ToString();
229             //初始化线程对象
230             T = new Thread(Work);
231             T.IsBackground = true;
232             working = true;
233             contextdata = new object();
234             //开启线程
235             T.Start();
236         }
237         //唤醒线程
238         public void Active()
239         {
240             locks.Set();
241         }
242         //设置执行委托和状态对象
243         public void SetWorkItem(WaitCallback action, object context)
244         {
245             taskWorkItem = action;
246             contextdata = context;
247         }
248         //线程体包装方法
249         private void Work()
250         {
251             while (working)
252             {
253                 //阻塞线程
254                 locks.WaitOne();
255                 taskWorkItem(contextdata);
256                 //完成一次执行,触发事件
257                 WorkComplete(this);
258             }
259         }
260         //关闭线程
261         public void Close()
262         {
263             working = false;
264         }
265         //回收资源
266         public void Dispose()
267         {
268             //throw new NotImplementedException();
269             try
270             {
271                 T.Abort();
272             }
273             catch { }
274         }
275     }
276 }

最后看看如何使用这个线程池类:

01 using System;
02 using System.Collections.Generic;
03 using System.Linq;
04 using System.Text;
05 using System.Threading;
06   
07 namespace TaskPoolTest
08 {
09     class Program
10     {
11         static void Main(string[] args)
12         {
13             TaskPool pool = new TaskPool();
14             for (int i = 0; i < 50; i++)
15             {
16                 pool.AddTaskItem(
17                     x =>
18                     {
19                         for (int j = 0; j < 5; j++)
20                         {
21                             Thread.Sleep(10);
22                             Console.WriteLine("Thread " + (int)x + " print " + j);
23                         }
24                     }, i);
25             }
26             Console.ReadKey();
27         }
28     }
29 }

执行的结果

image

 

posted @ 2010-01-19 01:10  冰封的心  阅读(255)  评论(0)    收藏  举报