• 一个自定义线程池的小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个,所以这个程序是可用的。

  • 相关阅读:
    发布时间 sql语句
    Excel中 查找重复数据
    身份证正则表达式
    (转)C#中的委托与事件
    C#中的ForEach
    Ajax请求中,contentType和dataType的区别
    让IIS支持PUT和Delete请求的方法
    Vue.js事件修饰符
    JS阻止默认行为
    关于bindinglist的一点小问题
  • 原文地址:https://www.cnblogs.com/dcz2015/p/5263006.html
Copyright © 2020-2023  润新知