C#线程池ThreadPool
线程池可以看做容纳线程的容器;
一个应用程序最多只能有一个线程池;
设置线程数量ThreadPool.SetMaxThreads(initDownCardThreadPool, maxDownCardThreadPool)
ThreadPool静态类通过QueueUserWorkItem()方法将工作函数排入线程池;
每排入一个工作函数,就相当于请求创建一个线程;
线程池的作用:
线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。
如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜。),况且我们还不能控制线程池中线程的开始、挂起、和中止。
什么时候使用ThreadPool?
ThreadPool 示例一 :
- ThreadPool_1.csCode highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->using System;
- using System.Text;
- using System.Threading;
- namespace 多线程
- {
- public class Example
- {
- public static void Main()
- {
- // Queue the task.
- ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
- Console.WriteLine("Main thread does some work, then sleeps.");
- Thread.Sleep(1000);
- Console.WriteLine("Main thread exits.");
- }
- static void ThreadProc(Object stateInfo)
- {
- // No state object was passed to QueueUserWorkItem,
- // so stateInfo is null.
- Console.WriteLine("Hello from the thread pool.");
- }
- }
- }
ThreadPool 示例二 :
- ThreadPool_2.csCode highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Threading;
- namespace CS_Test
- {
- class ThreadPool_Demo
- {
- // 用于保存每个线程的计算结果
- static int[] result = new int[10];
- //注意:由于WaitCallback委托的声明带有参数,
- // 所以将被调用的Fun方法必须带有参数,即:Fun(object obj)。
- static void Fun(object obj)
- {
- int n = (int)obj;
- //计算阶乘
- int fac = 1;
- for (int i = 1; i <= n; i++)
- {
- fac *= i;
- }
- //保存结果
- result[n] = fac;
- }
- static void Main(string[] args)
- {
- //向线程池中排入9个工作线程
- for (int i = 1; i <= 9 ; i++)
- {
- //QueueUserWorkItem()方法:将工作任务排入线程池。
- ThreadPool.QueueUserWorkItem(new WaitCallback(Fun),i);
- // Fun 表示要执行的方法(与WaitCallback委托的声明必须一致)。
- // i 为传递给Fun方法的参数(obj将接受)。
- }
- //输出计算结果
- for (int i = 1; i <= 9; i++)
- {
- Console.WriteLine("线程{0}: {0}! = {1}",i,result[i]);
- }
- }
- }
- }
ThreadPool的作用:
解决多线程编程中大并发数等待唤醒的问题
在移动交通流调查项目的一个算法分析程序中,碰到一个业务问题:用户采集上传的基站定位数据需要进行分析预处理,方案是先按预定格式解析文件并从中提取出成百上千个基站定位数据记录,并合并相同的基站点,根据获取到的基站位置信息作为参数,去请求google 基站定位 api,从而得到对应的基站定位经纬度等信息,接下来再加上华工的算法分析。
在执行华工算法分析逻辑之前,调用谷歌api这一步必需全部完成;网络请求是个耗时的过程,故对每一个请求开启单独的线程(同时请求可能数百个,这里通过Semaphore信号量来控制每次发出请求的最大数,该部分的讨论不再本话题之类)。
原理:封装一个ManualResetEvent对象,一个计数器current,提供SetOne和WaitAll方法;
主线程调用WaitAll方法使ManualResetEvent对象等待唤醒信号;
各个子线程调用setOne方法 ,setOne每执行一次current减1,直到current等于0时表示所有子线程执行完毕 ,调用ManualResetEvent的set方法,这时主线程可以执行WaitAll之后的步骤。
目标:减少ManualResetEvent对象的大量产生和使用的简单性。
在这里我写了个封装类:
- /********************************************************************************
- * Copyright © 2001 - 2010Comit. All Rights Reserved.
- * 文件:MutipleThreadResetEvent.cs
- * 作者:杨柳
- * 日期:2010年11月13日
- * 描述:封装 ManualResetEvent ,该类允许一次等待N(N>64)个事件执行完毕
- *
- * 解决问题:WaitHandle.WaitAll(evetlist)方法最大只能等待64个ManualResetEvent事件
- * *********************************************************************************/
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading;
- namespace TestMutipleThreadRestEvent
- {
- /// <summary>
- /// 封装ManualResetEvent
- /// </summary>
- public class MutipleThreadResetEvent : IDisposable
- {
- private readonly ManualResetEvent done;
- private readonly int total;
- private long current;
- /// <summary>
- /// 构造函数
- /// </summary>
- /// <param name="total">需要等待执行的线程总数</param>
- public MutipleThreadResetEvent(int total)
- {
- this.total = total;
- current = total;
- done = new ManualResetEvent(false);//done初始为非终止状态,有效状态,可以阻塞当前进程
- }
- /// <summary>
- /// 唤醒一个等待的线程
- /// </summary>
- public void SetOne()
- {
- // Interlocked 原子操作类 ,此处将计数器减1
- if (Interlocked.Decrement(ref current) == 0)
- {
- //当所以等待线程执行完毕时,唤醒等待的线程
- done.Set();
- }
- }
- /// <summary>
- /// 等待所以线程执行完毕
- /// </summary>
- public void WaitAll()
- {
- done.WaitOne();
- }
- /// <summary>
- /// 释放对象占用的空间
- /// </summary>
- public void Dispose()
- {
- ((IDisposable)done).Dispose();
- }
- }
- }
注释写的很清楚了:本质就是只通过1个ManualResetEvent 对象就可以实现同步N(N可以大于64)个线程
下面是测试用例:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading;
- namespace TestMutipleThreadRestEvent
- {
- /// <summary>
- /// 测试MutipleThreadResetEvent
- /// </summary>
- class Program
- {
- static int i = 0;
- /// <summary>
- /// 主方法
- /// </summary>
- /// <param name="args">参数</param>
- static void Main(string[] args)
- {
- //假设有100个请求线程
- int num = 100;
- //使用 MutipleThreadResetEvent
- using (var countdown = new MutipleThreadResetEvent(num))
- {
- for (int i=0;i<num;i++)
- {
- //开启N个线程,传递MutipleThreadResetEvent对象给子线程
- ThreadPool.QueueUserWorkItem(MyHttpRequest, countdown);//countdown为MyHttpRequest进入点函数提供参数
- }
- //等待所有线程执行完毕
- countdown.WaitAll();//主线程阻塞,直至处理完所有请求后,将<span style="font-family: 'ms shell dlg';">ManualResetEvent.Set(),置为终止状态,主线程可以运行</span>
- }
- Console.WriteLine("所有的网络请求以及完毕,可以继续下面的分析...");
- Console.ReadKey();
- }
- /// <summary>
- /// 假设的网络请求
- /// </summary>
- /// <param name="state">参数</param>
- private static void MyHttpRequest(object state)//state为加入进程池时传给<span style="font-family: 'ms shell dlg';">MyHttpRequest的参数countdown</span>
- {
- // Thread.Sleep(1000);
- Console.WriteLine(String.Format("哈哈:{0}",++i));
- MutipleThreadResetEvent countdown = state as MutipleThreadResetEvent;
- //发送信号量 本线程执行完毕
- countdown.SetOne();//只有当所有请求处理完后,count=0,才会将<span style="font-family: 'ms shell dlg';">ManualResetEvent置为终止状态</span>
- }
- }
- }
输出:
从结果上看线程执行的完成的时间顺序是不固定的;并且只有在所有100个网络请求任务完成后,才显示可以继续下面的分析。
与上面的方案是一样的效果,但是本方案使用非常简单,出错的概念小,免去了创建大量 ManualResetEvent 对象的烦恼
该解决方案可以适用与.net framework 2.0 以上的运行时。
tips:在.net framework 4.0 中有一个CountdownEvent对象可以实现类似的功能;
不过目前公司大多数项目运行时还是基于.net framework 2.0 和 3.5
注:ManualResetEvent详解
ManualResetEvent 允许线程通过发信号互相通信。通常,此通信涉及一个线程在其他线程进行之前必须完成的任务。当一个线程开始一个活动(此活动必须完成后,其他线程才能开始)时,它调用 Reset 以将 ManualResetEvent 置于非终止状态,此线程可被视为控制 ManualResetEvent。调用 ManualResetEvent 上的 WaitOne 的线程将阻止,并等待信号。当控制线程完成活动时,它调用 Set 以发出等待线程可以继续进行的信号。并释放所有等待线程。一旦它被终止,ManualResetEvent 将保持终止状态(即对 WaitOne 的调用的线程将立即返回,并不阻塞),直到它被手动重置。可以通过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态,如果初始状态处于终止状态,为 true;否则为 false。
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading;
- namespace ManualResetEventDemo
- {
- class MREDemo
- {
- private ManualResetEvent _mre;
- public MREDemo()
- {
- this._mre = new ManualResetEvent(true);
- }
- public void CreateThreads()
- {
- Thread t1 = new Thread(new ThreadStart(Run));
- t1.Start();
- Thread t2 = new Thread(new ThreadStart(Run));
- t2.Start();
- }
- public void Set()
- {
- this._mre.Set();
- }
- public void Reset()
- {
- this._mre.Reset();
- }
- private void Run()
- {
- string strThreadID = string.Empty;
- try
- {
- while (true)
- {
- // 阻塞当前线程
- this._mre.WaitOne();
- strThreadID = Thread.CurrentThread.ManagedThreadId.ToString();
- Console.WriteLine("Thread(" + strThreadID + ") is running...");
- Thread.Sleep(5000);
- }
- }
- catch(Exception ex)
- {
- Console.WriteLine("线程(" + strThreadID + ")发生异常!错误描述:" + ex.Message.ToString());
- }
- }
- }
- }
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace ManualResetEventDemo
- {
- class Program
- {
- static void Main(string[] args)
- {
- Console.WriteLine("****************************");
- Console.WriteLine("输入\"stop\"停止线程运行...");
- Console.WriteLine("输入\"run\"开启线程运行...");
- Console.WriteLine("****************************\r\n");
- MREDemo objMRE = new MREDemo();
- objMRE.CreateThreads();
- while (true)
- {
- string input = Console.ReadLine();
- if (input.Trim().ToLower() == "stop")
- {
- Console.WriteLine("线程已停止运行...");
- objMRE.Reset();
- }
- else if (input.Trim().ToLower() == "run")
- {
- Console.WriteLine("线程开启运行...");
- objMRE.Set();
- }
- }
- }
- }
- }