线程池(上):线程池的作用及CLR线程池(转)"/>
浅谈线程池(上):线程池的作用及CLR线程池(转)
2009-07-22 09:01 by Jeffrey Zhao, 7402 visits, 网摘, 收藏, 编辑
线程池是一个重要的概念。不过我发现,关于这个话题的讨论似乎还缺少了点什么。作为资料的补充,以及今后文章所需要的引用,我在这里再完整而又简单地谈一下有关线程池,还有.NET中各种线程池的基础。更详细的内容就不多作展开了,有机会我们再详细讨论这方面的细节。这次,还是一个“概述”性质的,希望可以说明白这方面问题的一些概念。
线程池的作用
其实“线程池”就是用来存放“线程”的对象池。
在程序中,如果某个创建某种对象所需要的代价太高,同时这个对象又可以反复使用,那么我们往往就会准备一个容器,用来保存一批这样的对象。于是乎,我们想要用这种对象时,就不需要每次去创建一个,而直接从容器中取出一个现成的对象就可以了。由于节省了创建对象的开销,程序性能自然就上升了。这个容器就是“池”。很容易理解的是,因为有了对象池,因此在用完对象之后必须有一个“归还”的动作,这样便可以把对象放回池中,下次需要的时候就可以再次拿出来使用了。
例如,我们在使用ADO.NET连接SQL Server时,.NET框架就会自动帮我们维护一个连接池,这就是因为重新创建一个连接的代价相对比较高昂,“复用”就显得比较划算了。不过有些朋友可能会说,我们明明是每次都创建一个SqlConnection对象,哪里有“复用”啊?这是因为.NET框架中把“连接池”做透明了,对于程序员完全隐藏了这个概念。每次我们虽然创建的是新的SqlConnection对象,但是这个对象内部占用的“数据库连接”还是会复用的。为什么总是强调用完SqlConnection对象后要及时“关闭”(Dispose或Close)呢?其实这里并没有断开数据库连接,只是把这个连接放回了连接池。等到下次创建新的SqlConnection对象时,这个连接又可以拿出来用了。
既然我们每次都是从池中获取对象,那么这些对象是由谁来创建,又是什么时候创建的呢?这个就要根据不同情况由各对象池来自行实现了。例如,可以在创建对象池的时候指定池内对象数量,并且一下子全部创建好,当然您也可以在得到请求时,如果发现池中已经没有剩余对象时创建。您也可以“事前”先准备一部分,“事中”根据需要再继续补充。还可以做得“智能”一些,例如,根据实际情况添加或删除一些对象,甚至对需求“走势”进行“预测”,在空闲时便创建更多的对象以备“不时之需”。各中变化难以言尽。
当然,它们的原理和目的是类似的。相信上面这段文字也已经讲清了“线程池”的作用:因为创建一个线程的代价较高,因此我们使用线程池设法复用线程。就是这么简单。
CLR线程池
在.NET中,CLR线程和操作系统线程对应,您可以简单地认为.NET中的Thread对象便封装了一个操作系统线程,并附带一些托管环境下所需要的数据(如GC Handle)1。而CLR线程池便是存放这些CLR线程的对象池。
我们在编写程序的时候,可以使用ThreadPool类的两个静态方法:QueueUserWorkItem和UnsafeUserQueueWorkItem向CLR线程池中添加任务(一个WorkCallback委托对象),这两个方法的区别,在于前者会收集调用方的ExecutionContext,也就是保留了的当前线程的执行信息(如认证或语言文化等),使任务最终会在“创建”时刻的环境中执行2——后者就不会。因此,如果比较两个方法的绝对性能,Unsafe方法会略胜一筹。但是平时还是建议使用QueueUserWorkItem方法,因为保留执行上下文会避免很多麻烦事情,且这点性能损耗其实算不上什么。
CLR线程池在.NET框架中的作用很大,除了让程序员使用之外,其他一些功能也会依赖CLR线程池。如ThreadPool.RegisterWaitForSingleObject方法,或是System.Threading.Timer组件——还有更重要可能也是更隐藏的:ASP.NET在得到一个请求后,也会将这个请求处理的任务交由CLR线程池去执行——请注意,它们最多只是添加任务而已,并不表示任务会立即执行。所有添加到CLR线程池的任务都会在合适的时候得以执行——可能马上,也可能要稍等片刻,甚至更久。
向CLR线程池添加任务时,任务会被临时放到一个队列中,并在合适的时候执行。那么怎么样才算是“合适的时候”?简单的概括说来,便是线程池内有空闲的线程,或线程池所管理的线程数量还没有达到上限的时候。如果有空闲的线程,线程池就会立即让它领取一个任务执行。如果是第二种情况,线程池便会创建新的Thread对象。由于让操作系统管理太多线程反而会造成性能下降,因此CLR线程池会有一个上限。不同的托管环境会设置不同的上限。如在.NET 2.0 SP1之后,普通的Windows应用程序(如控制台或WinForm/WPF),会将其设置为“处理器数 * 250”。也就是说,如果您的机器为2个2核CPU,那么CLR线程池的容量默认上限便是1000,也就是说,它最多可以管理1000个线程同时运行——很多情况下这已经是一个很可怕的数字了,如果您觉得这还不够,那么就应该考虑一下您的实现方式是否可以改进了。
对于ASP.NET应用程序来说,CLR线程池容量代表了应用程序最多可以同时执行的请求数量。对于托管在IIS上的ASP.NET执行环境来说,这个值由全局配置决定。这个配置在machine.config文件中system.web/processModel节点中,为maxWorkerThreads属性,它决定了为单个处理器分配的线程数。如果这个值为40,且机器上拥有4个处理器(2 * 2CPU),那么这台机器目前的配置表示在同一时刻,ASP.NET可以同时处理160个请求。某些参考资料建议您将其修改为每处理器80-100个线程,这时您只要修改相应的属性值就可以了。
既然有最大值,也就相应有了最小值,它代表了CLR线程池“总是会保留”的最少线程数量。由于线程会占用资源,如在默认情况下,每个线程将获得1MB大小的栈空间3。所以如果在系统中保留太多空闲线程对资源也是一种浪费。因此,CLR线程池在使用大量线程处理完大量任务之后,也会逐步地释放线程,直至到达最小值。CLR线程池的最小线程数量确保了在任务数量较少的情况下,新来的任务可以立即执行,从而省去了创建新线程的时间。在普通应用程序中这个值为“处理器数 * 1”,而在ASP.NET应用程序中这个值配置在machine.config文件中system.web/processModel节点的minWorkerThreads属性中4。
在某些时候可能会遇到这样的情况:在一个瞬间忽然来大量任务,每个任务的执行时间说长不长说短不短,不过足以导致线程池快速分配数百个线程。如果这个峰值之后就一片平静,那么势必造成大量空闲的线程,这种开销对性能的损耗也非常明显。因此,CLR线程池限制了线程的创建速度不超过每秒2个。这样,即使在某个瞬时获得了大量的任务,CLR线程池也可以使用相对较少的线程来完成所有工作5。
但是,还有一种情况也值得考虑。例如,对于一个比较繁忙的Web应用程序来说,一打开便会涌入大量的连接。由于线程的创建速度有限,因此可以执行的请求数量也只能慢慢增加。对于这种您预料到会产生大量线程,而且忙碌状况会持续一段时间的情况,限制线程的创建速度反而会带来损伤效率。这时,您就可以手动设置CLR线程池的最小线程数量。如果此时CLR线程池中拥有的线程数量较少,那么系统就会立即创建一定数量的线程来达到这个最小值。设置和获取CLR线程池最小线程数量的接口为:
public static class ThreadPool {public static void GetMinThreads(out int workerThreads, out int completionPortThreads);public static bool SetMinThreads(int workerThreads, int completionPortThreads); }
这两个接口的作用和使用方式应该足够明显了(不理解的话可以查阅MSDN),其中workerThreads参数便是CLR线程池的最小线程数,而completionPortThreads涉及到我们下次要讨论IO线程池,在此就不多作展开了。除了设置和读取CLR最小线程数的方法之外,ThreadPool还包含这些接口:
public static class ThreadPool {public static void GetMaxThreads(out int workerThreads, out int completionPortThreads);public static bool SetMaxThreads(int workerThreads, int completionPortThreads);public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads); }
值得注意的是,无论是设置还是获取到的这些数值,都与处理器数量没有任何关系了。也就是说,在一台2 * 2CPU的机器上运行一个普通的.NET应用程序时:
- 调用GetMaxThreads方法将获得1000,表示CLR线程池最大容量为1000(250 * 4),而不是250。
- 调用SetMinThreads并传入100,表示CLR线程池所拥有的最小线程数量为100,而不是400(100 * 4)。
对于CLR线程池的简单描述就暂时先到这里了。如果您还有什么疑问请提出,我会加以补充。
相关文章
- 浅谈线程池(上):线程池的作用及CLR线程池
- 浅谈线程池(中):独立线程池的作用及IO线程池
- 浅谈线程池(下):相关试验及注意事项
注1:严格说来,Thread对象和系统线程对应关系还有些细节上的考虑。例如,Thread对象只有当真正Start了之后,CLR才会创建一个操作系统线程与它绑定。
注2:ExecutionContext是个很重要且很有用的对象,例如,WinForms或WPF的异步任务中操作界面元素抛出异常该怎么办呢?
注3:使用Windows API或Thread类创建线程时可以指定它的栈空间大小,但是CLR线程池中的线程只能使用默认值——不过这个默认值也和托管环境有关,如普通应用程序默认为1MB,而ASP.NET为250KB,这意味着ASP.NET应用程序相对更容易产生Stack Overflow异常。
注4:可惜的是,对于processModel节点的数据,ASP.NET只会读取machine.config中的全局配置信息,这意味着我们不能使用web.config为不同应用程序配置不同的参数。如果我们要实现应用程序级别的配置,那么必须使用ThreadPool类中提供的API进行设置,这点稍后便会提到。
注5:对于这点,您不妨来做一个算术题:线程池内一下子涌入了500个任务,每个任务阻塞或暂停5秒,每个线程占用1MB内存,假设线程池目前为空,且有着足够的容量,此外线程创建速度也足够快,那么在限制及不限制线程创建速度的情况下,完成这些任务需要多少时间和内存空间?
17 2 0 (请您对文章做出评价) « 上一篇: 适合C# Actor的消息执行方式(4):阶段性总结» 下一篇: 浅谈线程池(中):独立线程池的作用及IO线程池
本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言。
Add your comment
54 条回复
1676717
-
#1楼 Galactica 2009-07-22 09:07
真会占位置,下篇就给个连接。 回复 引用 查看 -
#2楼[楼主] Jeffrey Zhao 2009-07-22 09:10
@Galactica
说明兄弟咱严谨,有计划。 回复 引用 查看 -
#3楼 青羽 2009-07-22 09:20
这个系列也很好啊。 回复 引用 查看 -
#4楼 killkill 2009-07-22 09:31
线程池是个好东西 回复 引用 查看 -
#5楼 wuyou[未注册用户]2009-07-22 09:34
咦,您怎么跑到这里来了。这可能是因为您在使用IE 6访问老赵的博客。
其实怎么说呢,作为一个技术人员,还在使用IE 6这样的浏览器是一个相当不专业的做法。
无论是功能/性能/安全性,IE 6都已经远远落后于这个时代标准。所以,抛弃它吧!
老赵建议您换成IE 8、FireFox、Chrome等浏览器。
嗯?您说是为了测试各种浏览器的情况才留着IE 6的吗?那老赵建议您使用IE Tester这个工具,然后……抛弃IE 6吧。
如果您有什么意见或建议,请给我留言。
对了,我帮您保留了原文链接,使用好一些的浏览器查看吧!
晕.至于嘛,我就爱IE6 回复 引用 -
#6楼 kenny.guo 2009-07-22 09:34
最近刚用到线程池的一些东西,看日食啦!! 回复 引用 查看 -
#7楼 seagreen7 2009-07-22 09:42
先占个位,慢慢消化 回复 引用 查看 -
#8楼[楼主] Jeffrey Zhao 2009-07-22 09:43
@wuyou
你爱IE 6没关系,不过看我的blog就换一个吧,嘿嘿。
最近越来越多的网站开始拒绝IE 6,咱也要推动历史发展。 回复 引用 查看 -
#9楼[楼主] Jeffrey Zhao 2009-07-22 09:52
@kenny.guo
上海下雨,看不到。 回复 引用 查看 -
#10楼 Leon Weng 2009-07-22 09:54
一直对多线程编程和网络编程很感兴趣,老赵有没有多线程方面的书可以推荐一下。 回复 引用 查看 -
#11楼[楼主] Jeffrey Zhao 2009-07-22 09:59
@Leon Weng
目前能够获得的最好的应该只有blog吧和其他一些网络资料了,比如我在边栏列举的Joe Duffy和MSDN Magazine的Concurrent相关专栏。 回复 引用 查看 -
#12楼 kenny.guo 2009-07-22 10:00
@ Jeffrey Zhao
杭州也只看到星星 回复 引用 查看 -
#13楼 Jerry Qian 2009-07-22 10:04
下链接没用 回复 引用 查看 -
#14楼 Otis's Technology Space 2009-07-22 10:04
之前老趙的文章都看不懂,現在這篇終於看懂了。。 ^_^. 回复 引用 查看 -
#15楼 Ray Z 2009-07-22 10:13
我代表党中央感谢老赵为普及计算机知识做出的巨大贡献! 回复 引用 查看 -
#16楼 Ryan Gene 2009-07-22 10:19
支持,那我顺便求教老赵一个问题:
有html代码:
<div style="width:400px;margin-right:1500px; border:1px solid blue; float:left;">
hello world
</div>
ie6, ff, chrome下没有滚动条,ie7,ie8下游横向滚动条,不知老赵有没有解决办法?
谢谢 回复 引用 查看 -
#17楼[楼主] Jeffrey Zhao 2009-07-22 10:36
预留的,嘿嘿。 回复 引用 查看 -
#18楼[楼主] Jeffrey Zhao 2009-07-22 10:36
@Ryan Gene
我不懂HTML/CSS……我也是遇到问题然后再设法解决的。 回复 引用 查看 -
#19楼 morbetter[未注册用户]2009-07-22 11:14
老赵,就是喜欢钻研技术.
学习了 回复 引用 -
#20楼 wenwuxianren 2009-07-22 11:15
哈哈,终于看到一个高手也不懂html/css了,我也不懂,但我不是高手。有些安慰,嘿嘿。不过在努力学习中 回复 引用 查看 -
#21楼 wuxiaoqqqq 2009-07-22 11:26
学习一下,概念都忘光了。 回复 引用 查看 -
#22楼 kgame[未注册用户]2009-07-22 11:50
Thread建構式不是有多載可以設定Stack Size
public Thread(ThreadStart start, int maxStackSize)
為何只能使用預設值
難道設定的了這值也仍然是1MB?
回复 引用 -
#23楼[楼主] Jeffrey Zhao 2009-07-22 11:53
@kgame
请看注3。 回复 引用 查看 -
#24楼 Galactica 2009-07-22 12:42
Thread是可以,但是没说ThreadPool也可以。
ThreadPool中的Thread是由ThreadPool创建的。 回复 引用 查看 -
#25楼 aloneplayer[未注册用户]2009-07-22 14:57
CLR线程池的最小线程数量确保了在任务数量较少的情况下,新来的任务可以立即执行,从而省去了创建新线程的时间。在普通应用程序中这个值为“处理器数 * 1”,而在ASP.NET应用程序中这个值配置在machine.config文件中system.web/processModel节点的 minWorkerThreads属性中4
我查阅了MSDN processModel Element (ASP.NET Settings Schema)
.aspx
minWorkerThreads的default值也是1,同时minWorkerThreads表示每个CPU分配的线程数的最小值,处理器数 * n的说法不对。
回复 引用 -
#26楼[楼主] Jeffrey Zhao 2009-07-22 15:33
@aloneplayer
我的意思是,普通应用程序不可配置,ASP.NET是可配置的。machine.config默认是1,你可以配置成其他值。
至于文档上说的CPU是指逻辑CPU。
2个物理CPU,每个CPU是2核,系统里认出来就是4个逻辑CPU。这点你尝试一下就知道了。
所以我的说法没有问题。 回复 引用 查看 -
#27楼 diggingdeeply_马甲[未注册用户]2009-07-22 16:12
=============================================
因为WIN32 API的CreateThread里可以设置StackSize,所以Thread的构造器里才可以设置,但是我不确定系统thread和托管的头thread的关系,这我得冷静冷静。
详见:
HANDLE WINAPI CreateThread(
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in_opt LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out_opt LPDWORD lpThreadId
);
想不到园子里还有TW的,建构式和多载头一回听说,长见识了。 回复 引用 -
#28楼 ping zhao[未注册用户]2009-07-22 16:58
坐看Jeffrey Zhao Install B.还有一群奴才在拍马屁! 回复 引用 -
#29楼[楼主] Jeffrey Zhao 2009-07-22 17:22
@ping zhao
不好意思,兄弟咱这儿有个规矩,登陆用户提供茶水饮料,匿名用户只能站墙角了。
当然,您可以选择“站看”,咱一样欢迎,嘿嘿。 回复 引用 查看 -
#30楼 DiryBoy 2009-07-22 17:54
补习一下:)~~
其实不懂什么是IO线程,还有貌似.Net4对线程池有改进,不过又好像是在调度方面……期待下篇! 回复 引用 查看 -
#31楼[楼主] Jeffrey Zhao 2009-07-22 18:45
@DiryBoy
.NET对线程池的功能,一是性能上有所增强,二是增加了Task相关的API,其他就没有什么变化了。
不过多线程方面的API增强倒很大。 回复 引用 查看 -
#32楼 勇赴 2009-07-22 19:50
框架维护的连接池,是不是ado也察觉不到呢?或者是由ado自己维护的,如果是,ado源码里怎么表现的? 回复 引用 查看 -
#33楼 Galactica 2009-07-22 20:11
勇赴:框架维护的连接池,是不是ado也察觉不到呢?或者是由ado自己维护的,如果是,ado源码里怎么表现的?
ado中的连接池是由ado自己维护,源代码请使用Reflector查看System.Data.dll或者System.Data.OracleClient.dll中System.Data.ProviderBase命名空间下的类,或者你也可以直接启用.Net Framework源代码调试跟踪,最后,你还可以直接翻看Mono或者SQLite源代码,当然SQLite的连接池实现比前面的在性能上还是有差距。 回复 引用 查看 -
#34楼 勇赴 2009-07-22 20:19
Galactica:
勇赴:框架维护的连接池,是不是ado也察觉不到呢?或者是由ado自己维护的,如果是,ado源码里怎么表现的?
ado中的连接池是由ado自己维护,源代码请使用Reflector查看System.Data.dll或者System.Data.OracleClient.dll中System.Data.ProviderBase命名空间下的类,或者你也可以直接启用.Net Framework源代码调试跟踪,最后,你还可以直接翻看Mono或者SQLite源代码,当然SQLite的连接池实现比前面的在性能上还是有差距。
很感谢 回复 引用 查看 -
#35楼 Collector 2009-07-22 20:32
非常感谢,你终于有一篇让我容易看懂的文章了。前面几篇看得我云山雾罩的,到处查资料还是没完全明白,根本不敢言语。 回复 引用 查看 -
#36楼 Collector 2009-07-22 20:51
看这篇文章就像听您WebCast讲课的感觉。 回复 引用 查看 -
#37楼[楼主] Jeffrey Zhao 2009-07-22 21:10
勇赴:框架维护的连接池,是不是ado也察觉不到呢?或者是由ado自己维护的,如果是,ado源码里怎么表现的?
我说的框架自己维护的连接池,就是指ADO.NET做的事情。 回复 引用 查看 -
#38楼[楼主] Jeffrey Zhao 2009-07-22 21:11
Collector:非常感谢,你终于有一篇让我容易看懂的文章了。前面几篇看得我云山雾罩的,到处查资料还是没完全明白,根本不敢言语。
前几篇哪有那么难懂的,我写作的方式从来没有变过,句式等等都是翻来复去那几样…… 回复 引用 查看 -
#39楼 不若相忘于江湖 2009-07-22 22:06
阿赵,
最近写作好像少了? 回复 引用 查看 -
#40楼[楼主] Jeffrey Zhao 2009-07-22 22:23
@不若相忘于江湖
大哥,7月份到现在已经发了9篇文章了,平均2天多就1篇,每篇3000字,你还要我多快阿…… 回复 引用 查看 -
#41楼 不若相忘于江湖 2009-07-22 23:17
Jeffrey Zhao:
@不若相忘于江湖
大哥,7月份到现在已经发了9篇文章了,平均2天多就1篇,每篇3000字,你还要我多快阿……
呵呵。 不好意思啊。 没有仔细看,我只注意到你发到首页的并且
我看到的 。老赵可不可以出本书啊。
回复 引用 查看 -
#42楼 不若相忘于江湖 2009-07-22 23:18
老赵,建议你你出本书,就讲一些架构设计和经验之谈,
估计会卖的火,因为现在市面上找不到这样的书。 回复 引用 查看 -
#43楼[楼主] Jeffrey Zhao 2009-07-22 23:55
@不若相忘于江湖
我写不来书啊,所以还是专注于博客吧。 回复 引用 查看 -
#44楼 yeml[未注册用户]2009-07-23 01:32
zhao,这里回你不知道你能不能看到。上次讲的那个关于Java里面,内嵌匿名类使用的上下文局部变量需要加final关键字的问题,我想了想,应该是这样的:我给你的那篇文章的说法应该是不对的,不对的地方是 :“使用final可以使局部变量提升为全局根”,我认为在java里,关于局部匿名类的实现机理应该与。net是一致的,即把匿名类方法使用到的上下文变量以及他的外部类的属性做成一个对象,传给匿名类方法。而java与。net的不同之处在于:由于java要求增加了final关键字,所以赋值过的变量不能被替换(但是可以被修改),这样就避免了在多线程的环境中,该变量被一条线程替换了(比如设为null),而另外一条线程又尝试去访问这个变量---注:因为在多线程环境里,代码的执行是不可预期的!这是我思考的结果,没有实际的去检查过(其实检查也简单,看看编译过后生成的那些类就应该可以知道的)。老赵,你觉得呢。 回复 引用 -
#45楼 yeml[未注册用户]2009-07-23 09:17
ThreadPool感觉没什么好写吧,写写注意事项就可以了,比如
1.asp里面不要用ThreadPool
2.怎样取消被queue的任务
3.。net框架很多异步都是用Threadpool实现的(。net支持对任何方法的异步调用)
4.介绍一种线程的回调方法。(SmartThreadPool在添加任务的时候即可以设定一个回调方法,而且该方法还支持返回值,不过ThreadPool就没这么方便了)
5. 暂时想到这么多,jeff zhao你再添几条咯,这是你的帖子 回复 引用 -
#46楼[楼主] Jeffrey Zhao 2009-07-23 09:44
@yeml
ThreadPool很需要写啊,上次发现不少朋友搞不清楚一些概念。
下篇会谈到注意事项,会提到一部分的。
至于ThreadPool的使用方式,就不准备谈了,这次谈概念,呵呵。 回复 引用 查看 -
#47楼[楼主] Jeffrey Zhao 2009-07-23 09:45
@yeml
这就是我的看法啊,当时好像你给了我一些资料,结果我的理解就是Java和C#的实现方式差不多的。 回复 引用 查看 -
#48楼 yeml[未注册用户]2009-07-23 10:42
6.又想到一个:主线程怎样等待扔进线程池的任务全部执行完。用。net的信号量+waitall不太好用,因为最多只能等待64个线程,上次有人写了一个CountDownLutch,还不赖 回复 引用 -
#49楼[楼主] Jeffrey Zhao 2009-07-23 10:51
@yeml
这些和线程池本身关系不大了,就是使用方面的问题,有机会再谈吧,这次就谈基本概念。
其实写这样一个CountDownLutch很容易,.NET 4.0也已经内置了。
.NET 4.0加了大量的轻量级的小类库,如SemaphoreSlim,SpinLock,SpinWait等等,不用像以前那样自己写了。 回复 引用 查看 -
#50楼 Galactica 2009-07-23 12:04
yeml:6.又想到一个:主线程怎样等待扔进线程池的任务全部执行完。用。net的信号量+waitall不太好用,因为最多只能等待64个线程,上次有人写了一个CountDownLutch,还不赖
使用WaitHandle[64]嵌套WaitHandle[64],以前在MFC下就是这么写的。
回复 引用 查看 -
#51楼 为了梦想 2009-07-23 22:05
基本的概念对于我们这些刚开始学的太重要了 回复 引用 查看 -
#53楼 Hello JeffreyZhao[未注册用户]2009-08-14 14:34
CLR Via C# 2nd上讲每个CPU的最大工作线程数量是25个,你的250是不是写错了 回复 引用 -
#54楼[楼主] Jeffrey Zhao 2009-08-14 14:40
@Hello JeffreyZhao
打过.NET 3.5 SP1就变成250了,.NET 2.0是25。 回复 引用 查看 -
#55楼 会飞的剑 2009-10-20 15:23
获益良多!! 回复 引用 查看
更多推荐
浅谈线程池(上):线程池的作用及CLR线程池(转)
发布评论