三个月,整整三个月了,我忽然发现我还有三个月前的一个小系列的文章没有结束,我还欠一个试验!线程池是.NET中的重要组件,几乎所有的异步功能依赖于线程池。之前我们讨论了线程池的作用、独立线程池的存在意义,以及对CLR线程池和IO线程池进行了一定说明。不过这些说明可能有些“抽象”,于是我们还是要通过试验来“验证”这些说明。此外,我认为针对某个“猜想”来设计一些试验进行验证是非常重要的能力,如果您这方面的能力略有不足的话,还是尽量加以锻炼并提高吧。

CLR线程的使用与创建

首先,我们准备这样一段代码:

public static void ThreadUseAndConstruction()
{ThreadPool.SetMinThreads(5, 5); // set min thread to 5ThreadPool.SetMaxThreads(12, 12); // set max thread to 12Stopwatch watch = new Stopwatch();watch.Start();WaitCallback callback = index =>{Console.WriteLine(String.Format("{0}: Task {1} started", watch.Elapsed, index));Thread.Sleep(10000);Console.WriteLine(String.Format("{0}: Task {1} finished", watch.Elapsed, index));};for (int i = 0; i < 20; i++){ThreadPool.QueueUserWorkItem(callback, i);}
}

这段代码很简单。首先将线程池最小和最大线程数量设为5和12,然后向线程池中连续推入20个任务,每个任务都是打印出执行时的当前时间,然后等待10秒钟。那么请您思考一下,这段代码的输出是什么样的呢?

展开

高位的零我们就直接忽略了,我们只观察“秒”及以下精度的时间。对这个数据进行简单观察之后,我们发现可以把时间精确到0.5秒来描述每个时刻所发生的事情:

  1. 0秒:任务0至任务3,共计4个任务开始执行。
  2. 1至3秒:任务4至任务8依次执行,间隔为0.5秒。
  3. 3至6秒:任务8至任务11依次执行,间隔为1秒。
  4. 10秒:任务0至任务3执行完成,任务12至任务15开始执行。
  5. 11至12.5秒:每执行完一个旧任务(4至7),便立即开始一个新任务(16至19)。
  6. 13至22.5秒:剩余任务(8至19)依次结束。  

您猜对了吗?我没有猜对,因为有两点:

  • 原来最小线程数量为5时,只有4个线程可以立即执行。经过进一步尝试,最小线程数量为10时,也只有9个线程可以立即执行。
  • 原来线程池创建线程的速度并非永远是“每秒2个”,而一些资料上写着“每秒不超过2个”的确是确切的说法。

但是,我们还是验证了以下几个结论:

  • 在线程池最小线程数量的范围之内,尽可能多的任务立即执行。
  • 线程池使用使用每秒不超过2个的频率创建线程(1秒一个或0.5秒一个)。
  • 当达到线程池最大线程数时(第6秒),停止创建新线程。
  • 在旧任务执行完毕后,新任务立即执行。

当然,由于我们在这之前已经“了解”了线程池是如何工作的,因此这里得到的结果可能会有“自圆其说”的倾向在里面。要减少这个可能性,则需要设计更完整的试验来“解释”问题。您也可以顺着这一点进行更深入的探索。

线程池中的线程是“公用”的

我们没有独立创建线程,而是选择使用线程池一定有其原因。不过,我们既然使用了线程池,就有一些额外的东西值得注意。

首先,我们要明确一个观念:线程并不“属于”任何一个任务,或者说任务并不“拥有”线程。我们只是借用一个线程来做事,用完以后便会还回。也就是说,任务在执行时修改线程的信息(名称,优先级,语言文化等等)是没有意义的,此外,任务也不应该依赖线程的这些状态。还记得上篇文章中谈到的QueueUserWorkItem和UnsafeQueueUserWorkItem之间的区别吗?如果您的任务需要依赖什么东西,也请自行准备。线程池中的线程状态是不可靠的。当然,也尽量不要直接对当前线程进行其他操作。

其次,由于线程池有大小限制,在某些时候还可能出现死锁的情况:

static void WaitCallback(object handle)
{ManualResetEvent waitHandle = (ManualResetEvent)handle;for (int i = 0; i < 10; i++){ThreadPool.QueueUserWorkItem(state =>{int index = (int)state;if (index == 9){waitHandle.Set(); // release all }else{waitHandle.WaitOne(); // wait }}, i);}
}public static void DeadLock()
{ManualResetEvent waitHandle = new ManualResetEvent(false);ThreadPool.SetMaxThreads(5, 5);ThreadPool.QueueUserWorkItem(WaitCallback, waitHandle);waitHandle.WaitOne();
}

在上面的代码中,waitHandle将永远阻塞。因为我们放入线程池的10个任务,只有最后一个会将waitHandle打开,其余任务也统统阻塞在这个waitHandle上。但是请注意,我们使用SetMaxThreads方法把最大线程数限制为5,这样第10个任务根本无法执行,从而进入了死锁。避免这个问题最简单的做法是增加最大线程数,但是这还是会产生许多无法工作的线程,造成资源的浪费。因此,最好的做法是重新设计并行算法,并且时刻记住:“不要阻塞线程池里的线程”。

如何合理而有效的使用线程(既不多也不少还不阻塞),这是并行算法中最常见的课题之一。例如,让您设计一个并行计算斐波那契数列的算法,如果您每次计算Fib(n)时,都创建两个新的任务来并行计算Fib(n - 1)和Fib(n - 2),并等待它们结束,就会造成上述的死锁(或大量线程)。如何解决这个问题?您可以观察一下.NET 4.0中新增的Task并行类库,它提供了丰富而易用的并行运算API,帮我们省去了大量的工作1

最后,便是时刻记得系统中哪些功能依赖线程池。例如ASP.NET中的请求也会使用CLR线程池,那么您是否应该使用ThreadPool?是否应该直接使用委托的异步调用?您是否应该调整线程池的最大和最小线数?这些问题没有确定答案,这需要您根据实际情况自己做判断。

CLR线程池与IO线程池

当第一次了解到.NET准备了一个CLR线程池和一个IO线程池的时后,我在想,这两者真的是没有关系的吗?他们会互相影响吗?于是我做了这么一个试验:

public static void IoThread()
{ThreadPool.SetMinThreads(5, 3);ThreadPool.SetMaxThreads(5, 3);ManualResetEvent waitHandle = new ManualResetEvent(false);Stopwatch watch = new Stopwatch();watch.Start();WebRequest request = HttpWebRequest.Create("http://www.cnblogs.com/");request.BeginGetResponse(ar =>{var response = request.EndGetResponse(ar);Console.WriteLine(watch.Elapsed + ": Response Get");}, null);for (int i = 0; i < 10; i++){ThreadPool.QueueUserWorkItem(index =>{Console.WriteLine(String.Format("{0}: Task {1} started", watch.Elapsed, index));waitHandle.WaitOne();}, i);}waitHandle.WaitOne();
}

得到的结果是这样的:

00:00:00.0923543: Task 0 started
00:00:00.1152495: Task 2 started
00:00:00.1153073: Task 3 started
00:00:00.1152439: Task 1 started
00:00:01.0976629: Task 4 started
00:00:01.5235481: Response Get

从中可以看出,我们将CLR线程池的最大线程数量设为了5,并使用与上一例类似的做法故意“阻塞”了线程池(而只有5个任务被执行了,说明线程池的确被阻塞了),其目的便是观察在这种情况下一个IO异步请求是否能够得到正确的回复。答案是肯定的,IO异步请求的回调函数正常执行了。这意味着,虽然CLR线程池被用完了,但是似乎的确还是有一个额外的IO线程池在处理IO的异步回调。这样看来,CLR线程池和IO线程池两者并没有影响。此外,从.NET框架所设计的类库来看,的确将两者作了区分,例如:

public static class ThreadPool
{public static bool GetAvailableThreads(out int workerThreads, out int completionPortThreads);
}

不过,这并不意味着CLR线程池中线程被用完之后,还是可以发起异步IO请求。例如,您可以尝试着将这个例子中的WebRequest操作放到for循环后面(确保CLR线程池中线程已经被用完了),这是您会发现BeginGetRequest方法的调用抛出了一个异常,提示您说线程池中没有多余的线程了。从这个角度这样看来,CLR线程池的确还是可能影响异步IO操作的(多谢xiongli大哥指出“这是由具体实现决定的”)——虽然这在普通应用程序中一般不会出现这个问题。

其实在IO线程池方面还可以进行其他一些试验。例如,您可以缩小IO线程池的最大线程数量,然后一下子发起多个异步IO请求,观察一下它们的回调函数执行时刻。这些不如就由您来自行完成了?

相关文章

  • 浅谈线程池(上):线程池的作用及CLR线程池
  • 浅谈线程池(中):独立线程池的作用及IO线程池
  • 浅谈线程池(下):相关试验及注意事项

注1:.NET 4.0在多线程方面进行了明显的增强,除了Task并行类库之外,也将Parallel Library并入框架之内。此外,.NET 4.0还提供了许多线程安全的并行容器,以及轻量级的CountDownLatch、SemaphoreSlim、SpinWait等常用组件,无论是学习还是使用都是绝佳的范例。

from: http://blog.zhaojie.me/2009/10/thread-pool-3-lab.html

浅谈线程池(下):相关试验及注意事项相关推荐

  1. 浅谈线程池(中):独立线程池的作用及IO线程池

    在上一篇文章中,我们简单讨论了线程池的作用,以及CLR线程池的一些特性.不过关于线程池的基本概念还没有结束,这次我们再来补充一些必要的信息,有助于我们在程序中选择合适的使用方式. 独立线程池 上次我们 ...

  2. 浅谈线程池(上):线程池的作用及CLR线程池

    线程池是一个重要的概念.不过我发现,关于这个话题的讨论似乎还缺少了点什么.作为资料的补充,以及今后文章所需要的引用,我在这里再完整而又简单地谈一下有关线程池,还有.NET中各种线程池的基础.更详细的内 ...

  3. ThreadPoolExecutor(五)——线程池关闭相关操作

    补充了和Thread的interrupt操作相关的知识,回头再来看ThreadPoolExecutor中interrupt,关闭线程池等相关操作. 1.shutdown /*** Initiates ...

  4. css中字体下划线样式,css下划线 浅谈css自定义下划线

    使用css样式对一段文字或一段文字中其中几个文字设置虚线效果的下划线如何实现?我们知道css字体下划线使用text-decoration样式实现,而虚线下划线则不能使用此css样式属性.要实现通过下边 ...

  5. 浅谈全局视角下的设计模式

    写在前面: 以下内容,更多的是自己的思考总结,不可避免出现有争议的地方,请谨慎食用. 浅谈全局视角下的设计模式 1.业务开发经常使用的设计模式有哪些? 2.为什么有些设计模式不常见呢? 3.为什么这些 ...

  6. 浅谈互联网时代下融媒技术现状

    浅谈互联网时代下融媒技术现状 摘要:近年来,我国数字技术的迅速发展使得媒体技术在"互联网+"时代下不断发展融合,形成了如今的融合媒体技术.新兴融媒技术的发展给广播电视行业带来了新的 ...

  7. java的向下转型_浅谈Java向下转型的意义

    一开始学习 Java 时不重视向下转型.一直搞不清楚向下转型的意义和用途,不清楚其实就是不会,那开发的过程肯定也想不到用向下转型. 其实向上转型和向下转型都是很重要的,可能我们平时见向上转型多一点,向 ...

  8. 浅谈用户密码保护与相关技术

    浅谈用户密码保护与相关技术(上) 一.  全文涉及 上篇:哈希,彩虹表 下篇:加盐加密,慢哈希,非对称加密与HTTPS 二.  主题引入 2011年12月21日,CSDN后台数据库被黑客恶意发布到互联 ...

  9. 浅谈虚拟化技术下的云安全如何处置

    浅谈虚拟化技术下的云安全如何处置 近年来,云计算是目前非常热门的一个研究领域,其实它并不是一种全新的技术,而是许多技术的融合体,包括分布式计算.动态和拓展等各种各样的技术算法,而虚拟化技术是云计算里最 ...

最新文章

  1. 【CV】Pytorch一小时教程添加损失函数图像可视化训练过程
  2. 对象是空的吗? [重复]
  3. Py之paddlehub:paddlehub的简介、安装、使用方法之详细攻略
  4. OpenCV提炼角点位置的实例(附完整代码)
  5. zookeeper的名词复盘-Watcher
  6. open 端口打开Linux,linux – nmap显示打开的端口,但netstat没有
  7. 网络飞鸽传书容易犯的错
  8. google search console的使用
  9. PyTorch安装问题解决
  10. StackExchange.Redis 官方文档(五) Keys, Values and Channels
  11. PX4 FMU [7] rgbled [转载]
  12. SetStretchBltMode() 防止图片失真
  13. 新闻叙事与文学影视叙事的区别
  14. linux查找不到kde桌面,观点|KDE Plasma 5 —— 给尚未确定桌面环境的 Linux 用户指明道路...
  15. 【强化学习】竞争深度Q网络(Dueling DQN)求解倒立摆问题 + Pytorch代码实战
  16. ubuntu系统下运行可执行文件 (application/x-executable)
  17. org.hibernate.SessionException: Session was already closed
  18. html代码可以在dw用吗,HTML基础DW使用教程(示例代码)
  19. 如何在一个页面上使用多个KindEditor编辑器并将值传递到服务器端
  20. 比较两数大小c语言,C语言比较两个数字的大小

热门文章

  1. java web临时文件删除_什么时候删除Java临时文件?
  2. hbuilder怎么做登录界面_hbuilder 第三方登录实例
  3. 江苏省二级python考试题库_python二级考试试题.doc
  4. python命令式编程的概念,【Python】十分钟学会函数式编程
  5. 瓦力机器人故障维修_大眼萌!5G巡逻机器人亮相乌镇,24小时值守互联网大会...
  6. mysql最高权限超级用户是_MySQL中,预设的、拥有最高权限超级用户的用户名为( )...
  7. 半监督分类算法_用图网络进行半监督分类
  8. python图片处理裁剪大小、旋转、镜像
  9. python tuple list_草根学Python(三)List 和 Tuple
  10. matlab需要多大运存_提高matlab运行效率