博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

转载来的线程池

Posted on 2008-05-29 14:58  wuhang  阅读(310)  评论(0编辑  收藏  举报

线程池的作用是什么 

作用是减小线程创建和销毁的开销

创建线程涉及用户模式和内核模式的切换,内存分配,dll通知等一系列过程,线程销毁的步骤也是开销很大的,所以如果应用程序使用了完一个线程,我们能把线程暂时存放起来,以备下次使用,就可以减小这些开销

所有进程使用一个共享的线程池,还是每个进程使用独立的线程池?

每个进程都有一个线程池,一个Process中只能有一个实例,它在各个应用程序域(AppDomain)是共享的,.Net2.0 中默认线程池的大 小为工作线程25个,IO线程1000个,有一个比较普遍的误解是线程池中会有1000个线程等着你去取,其实不然, ThreadPool仅仅保留相当 少的线程,保留的线程可以用SetMinThread这个方法来设置,当程序的某个地方需要创建一个线程来完成工作时,而线程池中又没有空闲线程时,线程 池就会负责创建这个线程,并且在调用完毕后,不会立刻销毁,而是把他放在池子里,预备下次使用,同时如果线程超过一定时间没有被使用,线程池将会回收线 程,所以线程池里存在的线程数实际是个动态的过程

为什么不要手动线程池设置最大值? 

当我首次看到线程池的时候,脑袋里的第一个念头就是给他设定一个最大值,然而当我们查看ThreadPool的SetMaxThreads文档时往往会看到一条警告:不要手动更改线程池的大小,这是为什么呢?

其实无论FileStream的异步读写,异步发送接受Web请求,甚至使用delegate的beginInvoke都会默认调用  ThreadPool,也就是说不仅你的代码可能使用到线程池,框架内部也可能使用到,更改的后果影响就非常大,特别在iis中,一个应用程序池中的所 有 WebApplication会共享一个线程池,对最大值的设定会带来很多意想不到的麻烦

线程池的线程为何要分类? 

线程池有一个方法可以让我们看到线程池中可用的线程数量:GetAvaliableThread(out workerThreadCount, out iocompletedThreadCount),对于我来说,第一次看到这个函数的参数时十分困惑,因为我期望这个函数直接返回一个整形,表明 还剩多少线程,这个函数居然一次返回了两个变量。

原来线程池里的线程按照公用被分成了两大类:工作线程和IO线程,或者IO完成线 程,前者用于执行普通的操作,后者专用于异步IO,比如文件和网络请求,注意,分类并不说明两种线程本身有差别,线程就是线程,是一种执行单元,从本质上 来讲都是一样的,线程池这样分类,举例来说,就好像某施工工地现在有1000把铁锹,规定其中25把给后勤部门用,其他都给施工部门,施工部门需要大量使 用铁锹来挖地基(例子土了点,不过说明问题还是有效的),后勤部门用铁锹也就是铲铲雪,铲铲垃圾,给工人师傅修修临时住房,所以用量不大,显然两个部门的 铁锹本身没有区别,但是这样的划分就为管理两个部门的铁锹提供了方便。

线程池中两种线程分别在什么情况下被使用,二者工作原理有什么不同? 

下面这个例子直接说明了二者的区别,我们用一个流读出一个很大的文件(大一点操作的时间长,便于观察),然后用另一个输出流把所读出的文件的一部分写到磁盘上 

我们用两种方法创建输出流,分别是 

创建了一个异步的流(注意构造函数最后那个true)

FileStream outputfs=new FileStream(writepath, FileMode.Create, FileAccess.Write, FileShare.None,256,true);

创建了一个同步的流 

FileStream outputfs = File.OpenWrite(writepath); 
 
然后在写文件期间查看线程池的状况

string readpath = "e:\\RHEL4-U4-i386-AS-disc1.iso";
string writepath = "e:\\kakakak.iso";
byte[] buffer = new byte[90000000];

//FileStream outputfs=new FileStream(writepath, FileMode.Create, FileAccess.Write, FileShare.None,256,true);
//Console.WriteLine("异步流");
//创建了一个同步的流

FileStream outputfs = File.OpenWrite(writepath);
Console.WriteLine("同步流");

 //然后在写文件期间查看线程池的状况

ShowThreadDetail("初始状态");

FileStream fs = File.OpenRead(readpath);

fs.BeginRead(buffer, 0, 90000000, delegate(IAsyncResult o)
{

    outputfs.BeginWrite(buffer, 0, buffer.Length,

    delegate(IAsyncResult o1)
    {

        Thread.Sleep(1000);

        ShowThreadDetail("BeginWrite的回调线程");

    }, null);

    Thread.Sleep(500);//this is important cause without this, this Thread and the one used for BeginRead May seem to be same one
},

null);


Console.ReadLine();

public static void ShowThreadDetail(string caller)
{
    int IO;
    int Worker;
    ThreadPool.GetAvailableThreads(out Worker, out IO);
    Console.WriteLine("Worker: {0}; IO: {1}", Worker, IO);
}


输出结果
异步流
Worker: 500; IO: 1000
Worker: 500; IO: 999
同步流
Worker: 500; IO: 1000
Worker: 499; IO: 1000
 
这两个构造函数创建的流都可以使用BeginWrite来异步写数据,但是二者行为不同,当使用同步的流进行异步写时,通过回调的输出我们可以看到,他使用的是工作线程,而非IO线程,而异步流使用了IO线程而非工作线程。

其实当没有制定异步属性的时候,.Net实现异步IO是用一个子线程调用fs的同步Write方法来实现的,这时这个子线程会一直阻塞直到调用完成.这 个子线程其实就是线程池的一个工作线程,所以我们可以看到,同步流的异步写回调中输出的工作线程数少了一,而使用异步流,在进行异步写时,采用了  IOCP方法,简单说来,就是当BeginWrite执行时,把信息传给硬件驱动程序,然后立即往下执行(注意这里没有额外的线程),而当硬件准备就 绪, 就会通知线程池,使用一个IO线程来读取。

.Net线程池有什么不足
没有提供方法控制加入线程池的线程:一旦加入线程池,我们没有办法挂起,终止这些线程,唯一可以做的就是等他自己执行

1)不能为线程设置优先级
2) 一个Process中只能有一个实例,它在各个AppDomain是共享的。ThreadPool只提供了静态方法,不仅我们自己添加进去的 WorkItem使用这个Pool,而且.net framework中那些BeginXXX、EndXXX之类的方法都会使用此Pool。
3)所支持的Callback不能有返回值。WaitCallback只能带一个object类型的参数,没有任何返回值。
4)不适合用在长期执行某任务的场合。我们常常需要做一个Service来提供不间断的服务(除非服务器down掉),但是使用ThreadPool并不合适。

下面是另外一个网友总结的什么不需要使用线程池,我觉得挺好,引用下来
  如果您需要使一个任务具有特定的优先级。
如果您具有可能会长时间运行(并因此阻塞其他任务)的任务。
如果您需要将线程放置到单线程单元中(所有 ThreadPool 线程均处于多线程单元中)。
如果您需要与该线程关联的稳定标识。例如,您应使用一个专用线程来中止该线程、将其挂起或按名称发现它。