C# ThreadPool

在多线程的程序中,经常会出现两种情况: 

      1. 应用程序中线程把大部分的时间花费在等待状态,等待某个事件发生,然后给予响应。这一般使用ThreadPool(线程池)来解决。

      2. 线程平时都处于休眠状态,只是周期性地被唤醒。这一般使用Timer(定时器)来解决。

线程 Thread 有两种:【前台线程】和【后台线程】,我们可以通过线程属性IsBackground 来指定线程的前后台属性(默认是前台线程);

两者的主要区别是:进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台线程,则会直接结束工作。

(一个重要注意事项是如果程序定义了一个不会完成的前台线程,主程序并不会正常结束)

.Net环境使用 Thread 建立的线程默认情况下为前台线程,即线程属性 IsBackground = false;在进程中,只要有一个前台线程未退出,进程就不会终止。主线程就是一个前台线程。后台线程不管线程是否结束,只要所有前台线程都退出(包括正常退出和异常退出)后,进程就会自动终止。一般后台线程用于处理时间较短的任务,如一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。而前台线程用来处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序,或是定时对某些系统资源进行扫描的程序。

在什么情况下使用线程池? 

      1.单个任务处理的时间比较短

      2.将需处理的任务的数量大 

      3.多线程的环境中,尽量采用线程池

线程池的大小

不管什么池,总有尺寸,ThreadPool也不例外。ThreadPool提供了4个方法来调整线程池的大小:

SetMaxThreads

GetMaxThreads

SetMinThreads

GetMinThreads

SetMaxThreads指定线程池最多可以有多少个线程,而GetMaxThreads自然就是获取这个值。SetMinThreads指定线程池中最少存活的线程的数量,而GetMinThreads就是获取这个值。

为何要设置一个最大数量和有一个最小数量呢?原来线程池的大小取决于若干因素,如虚拟地址空间的大小等。比如你的计算机是4G内存,而一个线程的初始堆栈大小为1M,那么你最多能创建4G/1M的线程(忽略操作系统本身以及其他进程内存分配);正因为线程有内存开销,所以如果线程池的线程过多而又没有被完全使用,那么这就是对内存的一种浪费,所以限制线程池的最大数是很make sense的。

那么最小数又是为啥?线程池就是线程的对象池,对象池的最大的用处是重用对象。为啥要重用线程,因为线程的创建与销毁都要占用大量的CPU时间。所以在高并发状态下,线程池由于无需创建销毁线程节约了大量时间,提高了系统的响应能力和吞吐量。最小数可以让你调整最小的存活线程数量来应对不同的高并发场景。

使用线程池的好处?

在多线程编程时,如果创建了过多的线程将会增加操作系统资源的占用,并且还要处理资源要求和潜在的占用冲突,并且使用了多线程之后将使代码的执行流程和资源竞争情况变得复杂,稍不留心就会产生Bug。在使用多线程编程时对需要同步的资源访问尤其需要注意,如系统资源(系统端口等)、共享资源(文件、窗口句柄等)、属于单个应用程序的资源(如全局、静态和实例字段或属性)。

针对上面的情况,我们可以使用线程池来解决上面的大部分问题,跟使用单个线程相比,使用线程池有如下优点:

1、缩短应用程序的响应时间。因为在线程池中有的线程处于等待分配任务状态(只要没有超过线程池的最大上限),无需创建线程。

2、不必管理和维护生存周期短暂的线程,不用在创建时为其分配资源,在其执行完任务之后释放资源。

3、线程池会根据当前系统特点对池内的线程进行优化处理。

4、减少在创建和销毁线程上所花的时间以及系统资源的开销,如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。


总之使用线程池的作用就是减少创建和销毁线程的系统开销。在.NET中有一个线程的类ThreadPool,它提供了线程池的管理。

ThreadPool 类是一个静态类,你不能也不必要生成它的对象。而且一旦使用该方法在线程池中添加了一个项目,那么该项目将是无法取消的。这里你无需自己建立线程,只需把你要做的工作写成函数,然后作为参数传递给ThreadPool.QueueUserWorkItem()方法就行了,传递的方法就是依靠 WaitCallback 代理对象,而线程的建立、管理、运行等工作都是由系统自动完成的,你无须考虑那些复杂的细节问题。

ThreadPool是一个静态类,它没有构造函数,对外提供的函数也全部是静态的。其中有一个QueueUserWorkItem方法,它有两种重载形式,如下:

public static bool QueueUserWorkItem(WaitCallback callBack):将方法排入队列以便执行。此方法在有线程池线程变得可用时执行。

public static bool QueueUserWorkItem(WaitCallback callBack,Object state):将方法排入队列以便执行,并指定包含该方法所用数据的对象。此方法在有线程池线程变得可用时执行。

QueueUserWorkItem方法中使用的的WaitCallback参数表示一个delegate,它的声明如下:

public delegate void WaitCallback(Object state)

如果需要传递任务信息可以利用WaitCallback中的state参数,类似于ParameterizedThreadStart委托。

ThreadPool 的用法: 

首先程序创建了一个 ManualResetEvent 对象,该对象就像一个信号灯,可以利用它的信号来通知其它线程。

ManualResetEvent 对象有几个重要的方法:

初始化该对象时,用户可以指定其默认的状态(有信号/无信号);

在初始化以后,该对象将保持原来的状态不变,直到它的 Reset() 或者 Set() 方法被调用: 

  Reset(): 

             将其设置为无信号状态;

  Set(): 

             将其设置为有信号状态。 

  WaitOne(): 

 使当前线程挂起,直到 ManualResetEvent 对象处于有信号状态,此时该线程将被激活。然后,程序将向线程池中添加工作项,这些以函数形式提供的工作项被系统用来初始化自动建立的线程。当所有的线程都运行完了以后,ManualResetEvent.Set() 方法被调用,因为调用了 ManualResetEvent.WaitOne() 方法而处在等待状态的主线程将接收到这个信号,于是它接着往下执行,完成后边的工作。

我们在一个程序中创建一个线程,安排给它一个任务,便交由操作系统来调度执行。操作系统会管理系统中所有的线程,并且使用一定的方式进行调度。什么是“调度”?调度便是控制线程的状态:执行,等待等等。我们都知道,从理论上来说有多少个处理单元(如2 * 2 CPU的机器便有4个处理单元),就表示操作系统可以同时做几件事情。但是线程的数量会远远超过处理单元的数量,因此操作系统为了保证每个线程都被执行,就必须等一个线程在某个处理器上执行到某个情况的时候,“换”一个新的线程来执行,这便是所谓的“上下文切换(Context Switch)”。至于造成上下文切换的原因也有多种,可能是某个线程的逻辑决定的,如遇上锁,或主动进入休眠状态(调用Thread.Sleep方法),但更有可能是操作系统发现这个线程“超时”了。在操作系统中会定义一个“时间片(timeslice)“,当发现一个线程执行时间超过这个时间,便会把它撤下,换上另外一个。这样看起来,多个线程——也就是多个任务在同时运行了。值得一提的是,对于Windows操作系统来说,它的调度单元是线程,这和线程究竟属于哪个进程并没有关系。

QueueUserWorkItem这个技术存在许多限制。其中最大的问题是没有一个内建的机制让你知道操作在什么时候完成,也没有一个机制在操作完成时获得一个返回值,这些问题使得我们都不敢启用这个技术。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,293评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • 这个冬天,好像少雨,今天起来的时候,好不容易看到地面有些湿,但还不足以让老妈放心的程度,因为她菜籽撒在地上,长不出...
    张仁女阅读 347评论 0 0
  • 一:熟悉产品 1.认真学习考察技术,方便跟同行介绍我们的产品,容易清楚的判断出客户能不能做,能做多少。 2.提升工...
    路娟娟阅读 415评论 0 0
  • 01 我不喜欢你总是一副胸有成竹的样子,好像我没你不行。 可事实就是这样,只要你一找我,我立马就不记得上一秒发过的...
    Ruueryee阅读 539评论 0 0