C#线程基础在前几篇博文中都介绍了,现在最后来挖掘一下线程池的管理机制,也算为这个线程基础做个完结。

  我们现在都知道了,线程池线程分为工作者线程和I/O线程,他们是怎么管理的?

  对于Microsoft设计的CLR线程池,线程池会随着CLR的每个版本的发布,都会发生变化,很难去挖掘,这里的提议是:

  最好将线程看成一个黑盒。不要拿单个应用程序去衡量这个黑盒的性能,因为它对任何一个应用程序来说都无法做到完美。

  相反,它是一种常规用途的线程调度技术,面向大量应用程序;它对某些应用程序的效果要好于其他应用程序。

目前,它的工作情况非常理想,这里建议你信任它,因为你很难高出一个比CLR自带的那个更好的线程池。另外,随着时间的推移,线程池代码内部,会更改它管理线程的方式,所以大多数应用程序的性能会变得越来越好。

  CLR允许开发人员设置线程池创建最大线程数。然后有些开发人员感觉好像有必要对线程池拥有的线程数量进行限制,因为有些人觉得,要合理利用资源,做到自己调配资源,是很有成就感的事(是不是强迫症?)

但实践证明,线程池永远都不应该为池中的线程数设置上限,因为可能发生饥饿或死锁。

为什么这么说?

  假如队列中有1000个工作项,但这些工作项全都因为一个事件而阻塞(多么可怕的事),等到第1001个工作项发出信号才能解除阻塞。如果设置最大1000个线程,第1001个线程就不会执行,所以1000个线程会一直阻塞,然后你能想到的,用户被迫终止应用程序,并丢失他们的所有未保存的工作。你不能让线程阻塞!

  由于存在饥饿和死锁问题,所以CLR团队一直都在稳步的增加线程池默认能拥有的最大线程数。

  目前默认值是最大1000个。这可以看成是不限数量,为什么?

  一个32位进程最大的2GB的可用地址空间,加载了一组Win32和CLR DLLs,并分配了本地堆和托管堆之后,剩余约1.5GB的地址空间。由于每个线程都要为用户模式栈和线程环境块准备超过1MB的内存,所以在一个32位的进程中,最多能有1360个线程。试图创建更多线程,则会抛出OutMemoryException。

  一个64位进程提供了8TB的地址空间,所以理论上可以创建千百万个线程。但是分配这么多线程,纯属浪费,尤其是当理想线程数等于机器的CPU数的时候。

  ThreadPool类提供了几个静态方法,调用它们可以设置和查询线程池的线程数:GetMaxThreads,SetMaxThreads,GetMinThreadsGetAvailableThreads。这里建议你,不要调用上述任何方法,限制线程池的线程数,一般只会造成应用程序的性能变得更差,而不会变得更好

  如果你认为自己的应用程序需要几百个或者几千个线程,那只表明,你的应用程序的架构和使用线程的方式已出现严重的问题。

现在来看看如何管理工作者线程,之前需要来看看CLR线程池是什么样的:

这是工作者线程的数据结构。ThreadPool.QueueUserWorkItem方法和Timer类总是会将工作项放到全局队列中。

而工作线程采用一个先入先出(FIFO)算法将工作项从这个队列取出,并处理它们。(学过数据结构的应该知道FIFO)

由于多个工作者线程可能同时从全局队列中拿走工作项,所以所有工作者线程都竞争一个线程同步锁,以保证两个或多个线程不会获取同一个工作项。同步锁在某些应用程序总可能对伸缩性和性能造成某种程度的限制。

  当一个非工作者线程调度一个Task时,Task会添加到全局队列。但是,每个工作者线程都有它自己的本地队列,上图可以看到,工作者线程是主,对应的本地队列是附,当一个工作者线程调度一个Task时,Task会添加到调用线程的本地队列,而不是全局队列。

  现在来看下工作者线程的描述:

  工作者线程之所以称为Workers,它是名副其实的。它就是一“工作狂”,打个比方:

  工作狂是什么?做完自己的事还不够,还要去抢别人的事做,别人的事做完了,就去找公共的事做,除非没有事干,要不然不会停下。

  用这个比方,下面我的介绍就会浅显很多了。

  一个工作者线程准备处理一个工作项时,它总是先检查它的本地队列来查找一个Task。如果存在Task,工作者线程就从它的本地队列中移除Task,并对工作项进行处理。

  要注意的是,工作者线程是采用一个“栈”式结构,也就是后入先出(LIFO)算法,将任务从它的本队队列中取出。由于工作者线程是唯一允许访问自己的本地队列头的线程,所以不需要同步锁,而且在队列中添加和删除任务的速度非常快,这个行为的副作用就是,它的执行顺序是相反的,后入的先执行。

  还有哦,如果一个工作者线程发现本地队列变空了,那么它就会尝试从另一个工作者线程的本地队列中“偷”一个Task,并获取一个线程同步锁,不过这种情况还是很少发生的。

  再是,当所有本地队列都为空了,工作者线程就使用FIFO算法,从全局队列中提取一个工作项,当然也会取得它的锁。

  现在所有队列都为空了,工作者线程就会自己进入睡眠状态,等待事情的发生。如果睡眠了时间太长,它会自己醒来,并销毁自身。

  线程池会快速创建工作者线程,工作者线程的数量等于ThreadPool的SetMinThreads方法的值(默认是你的电脑CPU数),32位进程最多用32个CPU,64位进程最多可用64个CPU。然后创建工作者线程达到机器CPU数时,线程池会监视工作项的完成速度,如果工作项完成的时间太长,线程池就会创建更多的工作者线程,使工作加速完成。如果工作项的完成速度开始变快了,工作者线程就会被销毁。

  线程池的设计是很人性话的,有没有体会到?

  线程基础用了这么久才介绍完,新的起点又来啦。^_^

C#线程篇---线程池如何管理线程(6完结篇)的更多相关文章

  1. C# 线程(二):关于线程的相关概念

    From : http://kb.cnblogs.com/page/42528/ 什么是进程? 当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源. 而一个进程又 ...

  2. 【Linux 线程】同一个进程中的线程共享哪些资源

    进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线 ...

  3. C# 多线程的自动管理(线程池) 基于Task的方式

    C# 多线程的自动管理(线程池) 在多线程的程序中,经常会出现两种情况:    1. 应用程序中线程把大部分的时间花费在等待状态,等待某个事件发生,然后给予响应.这一般使用 ThreadPool(线程 ...

  4. 使用ExecutorCompletionService 管理线程池处理任务的返回结果

    在我们日常使用线程池的时候,经常会有需要获得线程处理结果的时候.此时我们通常有两种做法. 1. 使用并发容器将callable.call() 的返回Future存储起来.然后使用一个消费者线程去遍历这 ...

  5. ExecutorService实际上是一个线程池的管理工具

    在Java5之后,并发线程这块发生了根本的变化,最重要的莫过于新的启动.调度.管理线程的一大堆API了.在Java5以后,通过Executor来启动线程比用 Thread的start()更好.在新特征 ...

  6. C#多线程学习(四) 多线程的自动管理(线程池)

    在多线程的程序中,经常会出现两种情况: 一种情况:   应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应                   这一般使用ThreadPo ...

  7. Python之路(第四十六篇)多种方法实现python线程池(threadpool模块\multiprocessing.dummy模块\concurrent.futures模块)

    一.线程池 很久(python2.6)之前python没有官方的线程池模块,只有第三方的threadpool模块, 之后再python2.6加入了multiprocessing.dummy 作为可以使 ...

  8. 线程池的管理类MyThreadPoolManager

    import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executor; import java.ut ...

  9. 如何应用CLR线程池来管理多线程

        class Program     {         static void Main(string[] args)         {             int intWorkerT ...

随机推荐

  1. tf导出pb文件,以及如何使用pb文件

    先罗列出来代码,有时间再解释 from tensorflow.python.framework import graph_util import tensorflow as tf def export ...

  2. Python基础系列讲解——继承派生和组合的概念剖析

    Python作为一门面向对象的语言,它的面向对象体系中主要存在这么两种关系,一个是“类”和“实例”的关系,另一个是“父类”和“子类”的关系. 所谓“类”是从一堆对象中以抽象的方式把相同的特征归类得到的 ...

  3. 利用Cocoapods创建基于SVN的私有库podspec

    由于项目年后要进行组件化,考虑到公司内部实现的一些私有组件,不对外公开,而又想在不同项目中使用,该怎么办呢?由于cocoapods有了强大的功能,可以自己创建podspec,更可以设置私有的库.那么利 ...

  4. 网页调起App之应用实践

    声明:本文由入驻搜狐公众平台的作者撰写,除搜狐官方账号外,观点仅代表作者本人,不代表搜狐立场.举报 新春佳节即将到来,北京的上地&西二旗.望京&国贸.五道口&中关村地区等程序员 ...

  5. 《Spring1之第六次站立会议》

    <第六次站立会议> 昨天:向小伙伴们请教了我代码的错误的解决方法以及对TCP/IP协议进行了相关的了解. 今天:我把自己项目工程里的服务器端界面进行了优化和完善. 遇到的问题:觉得做的界面 ...

  6. b1

    组长:吴晓晖 过去两天完成了哪些任务: 代码重构进行中,界面,预计两个beta单位完成 展示GitHub当日代码/文档签入记录 接下来的计划 更加人性化的推荐算法 还剩下哪些任务 有哪些困难 有哪些收 ...

  7. matconvnet编译

    1.安装matconvnet 网上教程很多 2.编译 cd matconvnet/ addpath matlab/ vl_compilenn('enableGpu',true,'cudaRoot',' ...

  8. 12_Java面向对象_第12天(构造方法、this、super)_讲义

    今日内容介绍 1.构造方法 2.this关键字 3.super关键字 4.综合案例 01构造方法引入 A:构造方法的引入 在开发中经常需要在创建对象的同时明确对象的属性值, 比如员工入职公司就要明确他 ...

  9. PAT 甲级 1155 Heap Paths

    https://pintia.cn/problem-sets/994805342720868352/problems/1071785408849047552 In computer science, ...

  10. Jvm dump介绍与使用(内存与线程)

    很多情况下,都会出现dump这个字眼,java虚拟机jvm中也不例外,其中主要包括内存dump.线程dump. 当发现应用内存溢出或长时间使用内存很高的情况下,通过内存dump进行分析可找到原因. 当 ...