线程池(C#)

转自:http://blog.sina.com.cn/s/blog_494305f30100ryw7.html

在这里你可以学到Microsoft研究CLR实现线程池的原理机制,从而更灵活的处理CLR在实际代码应中线程池的问题,下面我们来看看吧。

CLR教程之线程池的产生

当 CLR 初始化时,其线程池中不含有线程。当应用程序要创建线程来执行任务时,该应用程序应请求线程池线程来执行任务。线程池知道后将创建一个初始线程。

该新线程经历的初始化和其他线程一样;但是任务完成后,该线程不会自行销毁。相反,它会以挂起状态返回线程池。如果应用程序再次向线程池发出请求,那么这个挂起的线程将激活并执行任务,而不会创建新线程。这节约了很多开销。

只要线程池中应用程序任务的排队速度低于一个线程处理每项任务的速度,那么就可以反复重用同一线程,从而在应用程序生存期内节约大量开销。

如果线程池中应用程序任务排队的速度超过一个线程处理任务的速度,则线程池将创建额外的线程。当然,创建新线程确实会产生额外开销,但应用程序在其生存期中很可能只请求几个线程来处理交给它的所有任务。因此,总体来说,通过使用线程池可以提高应用程序的性能。

线程池的一个绝妙特性是:它是启发式的。如果您的应用程序需要执行很多任务,那么线程池将创建更多的线程。如果您的应用程序的工作负载逐渐减少,那么线程池线程将自行终止。线程池的算法确保它仅包含置于其上的工作负荷所需要的线程数!

因此,希望您现在已理解了线程池的基本概念,并明白了它所能提供的性能优势。现在我将给出一些代码来说明如何使用线程池。首先,您应该知道线程池可以提供四种功能:

1> 异步调用方法

2> 以一定的时间间隔调用方法

3> 当单个内核对象得到信号通知时调用方法

4> 当异步 I/O 请求结束时调用方法

前三种功能非常有用,我将在本专栏中加以说明。而应用程序开发人员很少使用第四种功能,因此在此我将不做说明;有可能在将来的专栏中讲到。

功能 1:CLR线程池教程之异步调用方法

在您的应用程序中,如果有创建新线程来执行任务的代码,那么我建议您用命令线程池执行该任务的新代码来替换它。事实上,您通常会发现,让线程池执行任务比让一个新的专用线程来执行任务更容易。

要排队线程池任务,您可以使用 System.Threading 命名空间中定义的 ThreadPool 类。(ThreadPool 类只提供静态方法,且不能构造它的实例)

要让线程池线程异步调用方法,您的代码必须调用一个 ThreadPool 的重载 QueueUserWorkItem() 方法,如下所示:

public static Boolean QueueUserWorkItem(WaitCallback wc, Object state);

public static Boolean QueueUserWorkItem(WaitCallback wc);

这些方法将“工作项”(和可选状态数据)排队到线程池的线程中,并立即返回。工作项只是一种方法(由 wc 参数标识),它被调用并传递给单个参数,即状态(状态数据)

没有状态参数的 QueueUserWorkItem 版本将 null 传递给回调方法。最后,池中的某些线程将调用您的方法来处理该工作项。

注意:回调方法必须与 System.Threading.WaitCallback 委托类型相匹配,其定义如下:

public delegate void WaitCallback(Object state);

代码示例:

[csharp] view plaincopy

  1. using System;
  2. using System.Threading;
  3. public class Test
  4. {
  5. // 存放要计算的数值的字段
  6. static double dRes1 = -1;
  7. static double dRes2 = -1;
  8. public static void Main()
  9. {
  10. // 获取线程池的最大线程数和维护的最小空闲线程数
  11. int maxThreadNum, portThreadNum;
  12. int minThreadNum;
  13. ThreadPool.GetMaxThreads(out maxThreadNum, out portThreadNum);
  14. ThreadPool.GetMinThreads(out minThreadNum, out portThreadNum);
  15. Console.WriteLine("最大线程数:{0}", maxThreadNum);
  16. Console.WriteLine("最小空闲线程数:{0}", minThreadNum);
  17. // 函数变量值
  18. int x = 15600;
  19. // 启动第一个任务:计算x的8次方
  20. Console.WriteLine("启动第一个任务:计算{0}的8次方。", x);
  21. ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc1), x);
  22. // 启动第二个任务:计算x的8次方根
  23. Console.WriteLine("启动第二个任务:计算{0}的8次方根。", x);
  24. ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc2), x);
  25. // 等待,直到两个数值都完成计算
  26. while (dRes1 == -1 || dRes2 == -1) ;
  27. // 打印计算结果
  28. Console.WriteLine("{0}的8次方和8次方根分别为{1}, {2}", x, dRes1, dRes2);
  29. }
  30. // 启动第一个任务:计算x的8次方
  31. static void TaskProc1(object o)
  32. {
  33. dRes1 = Math.Pow(Convert.ToDouble(o), 8);
  34. }
  35. // 启动第二个任务:计算x的8次方根
  36. static void TaskProc2(object o)
  37. {
  38. dRes2 = Math.Pow(Convert.ToDouble(o), 1.0 / 8.0);
  39. }
  40. }

功能 2:CLR线程池教程之以一定的时间间隔调用方法

如果您的应用程序需要在某一时间执行某项任务,或者您的应用程序需要定期执行某些方法,那么使用线程池将是您的最佳选择。

System.Threading 命名空间定义 Timer 类。当您构造 Timer 类的实例时,您是在告诉线程池您想在将来的某个特定时间回调自己的某个方法。

Timer 类有四种构造函数:

public Timer(TimerCallback callback, Object state, Int32 dueTime, Int32 period);

public Timer(TimerCallback callback, Object state, UInt32 dueTime, UInt32 period);

public Timer(TimerCallback callback, Object state, Int64 dueTime, Int64 period);

public Timer(TimerCallback callback, Object state, Timespan dueTime, TimeSpan period);

所有这四种构造函数构造完全相同的 Timer 对象。回调参数标识您想由线程池线程回调的方法。

注意:回调方法必须与 System.Threading.TimerCallback 委托类型相匹配,其定义如下:

public delegate void TimerCallback(Object state);

构造 Timer 对象后,线程池知道要做什么,并自动为您监视时间。

然而,Timer 类还提供了几种其他的方法,允许您与线程池进行通信,以便更改什么时候(或者是否)应当回调方法。具体地说,Timer 类提供了几种 Change 和 Dispose 方法:

public Boolean Change(Int32 dueTime, Int32 period);

public Boolean Change(UInt32 dueTime, UInt32 period);

public Boolean Change(Int64 dueTime, Int64 period);

public Boolean Change(TimeSpan dueTime, TimeSpan period);

public Boolean Dispose(); public Boolean Dispose(WaitHandle notifyObject);

Change 方法允许您更改 Timer 对象的 dueTime 和 period;

Dispose 方法允许您在所有挂起的回调已经完成的时候,完全取消回调,并可选地用信号通知由 notifyObject 参数标识的内核对象。

功能 3:CLR线程池教程之当单个内核对象得到信号通知时调用方法

要让线程池线程在内核对象得到信号通知时调用您的回调方法,您可以再次利用 System.Threading.ThreadPool 类中定义的一些静态方法。

要让线程池线程在内核对象得到信号通知时调用方法,您的代码必须调用一个重载的 RegisterWaitHandle 方法

作用:注册一个等待 WaitHandle 的委托

函数原型:

public static RegisteredWaitHandle RegisterWaitForSingleObject(
	WaitHandle waitObject,
	WaitOrTimerCallback callBack,
	Object state,
	int millisecondsTimeOutInterval,
	bool executeOnlyOnce
)

参数:

1> waitObject 标识出您想要线程池等待的内核对象。

由于该参数是抽象基类 System.Threading.WaitHandle,因此您可以指定从该基类派生出来的任何类。

特别地,您可以将一个引用传递给 AutoResetEvent、ManualResetEvent 或 Mutex object

2> callBack 标识出您想要线程池线程调用的方法。

注意:回调方法必须与 System.Threading.WaitOrTimerCallback 委托类型相匹配,其定义如下列代码行所示:

public delegate void WaitOrTimerCallback(Object state, Boolean timedOut);

3> state 允许您指定应传递给回调方法的某些状态数据,如果没有特别的状态数据要传递,则传递 null;

4> milliseconds 允许您告诉线程池内核对象得到信号通知前应该等待的时间。这里通常传递 -1,以表示阻塞等待。

5> executeOnlyOnce指出回调函数为执行多次还是一次

如果取值为真,那么线程池线程将仅执行回调方法一次;

如果取值为假,那么线程池线程将在内核对象每次得到信号通知时执行回调方法。这对 AutoResetEvent 对象非常有用。

返回值:

该方法返回一个 RegisteredWaitHandle 对象。该对象确定线程池在等待的内核对象。如果由于某种原因,您的应用程序要告诉线程池停止监视已注册的等待句柄,那么您的应用程序就可以调用 RegisteredWaitHandle 的 Unregister 方法:

public Boolean Unregister(WaitHandle waitObject);

waitObject 参数表明当执行完队列中的所有工作项后,您想如何得到信号通知。

如果不想得到信号通知,那么您应将 null 传递给该参数。如果您将一个有效引用传递给 WaitHandle-derived 对象,那么线程池会在已注册等待句柄的所有挂起工作项执行完后,通知该对象。

小结

在本专栏中,我讲述了对线程池的需要,说明了如何利用 CLR 线程池提供的各种功能。现在您应该明白线程池为您的开发所带来的价值,它可以提高您的应用程序的性能,并简化您的代码。

时间: 2024-10-24 12:10:49

线程池(C#)的相关文章

Java四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor

介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? Java new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } }).start(); 1 2 3 4 5 6 7 new Thread(new

线程的控制和线程池

一.WaitHandle: ”.Net 中提供了一些线程间更自由通讯的工具,他们提供了通过"信号"进行通讯的机制 可以通过ManualResetEvent,AutoResetEvent(他是在开门并且一个 WaitOne 通过后自动关门)来进行线程间的通讯 waitOne:    等待开门 Set:           开门 Reset:       关门 static void Main(string[] args) { ManualResetEvent mre = new Manu

内存池、进程池、线程池

首先介绍一个概念"池化技术 ".池化技术 一言以蔽之就是:提前保存大量的资源,以备不时之需以及重复使用. 池化技术应用广泛,如内存池,线程池,连接池等等.内存池相关的内容,建议看看Apache.Nginx等开源web服务器的内存池实现. 起因:由于在实际应用当中,分配内存.创建进程.线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作.           因此,当程序中需要频繁的进行内存申请释放,进程.线程创建销毁等操作时,通常会使用内存池.进程池.

缓冲池,线程池,连接池

SSH:[email protected]:unbelievableme/object-pool.git   HTTPS:https://github.com/unbelievableme/object-pool.git 缓冲池 设计要点:包含三个队列:空缓冲队列(emq),装满输入数据的输入的队列(inq),装满输出数据的输出队列(outq),输入程序包括收容输入(hin),提取输入(sin),输出程序包括收容输出(hout)和提取输出(sout). 注意点:输入程序和输出程序会对缓冲区并发访

记5.28大促压测的性能优化—线程池相关问题

目录: 1.环境介绍 2.症状 3.诊断 4.结论 5.解决 6.对比java实现 废话就不多说了,本文分享下博主在5.28大促压测期间解决的一个性能问题,觉得这个还是比较有意思的,值得总结拿出来分享下. 博主所服务的部门是作为公共业务平台,公共业务平台支持上层所有业务系统(2C.UGC.直播等).平台中核心之一的就是订单域相关服务,下单服务.查单服务.支付回调服务,当然结算页暂时还是我们负责,结算页负责承上启下进行下单.结算.跳支付中心.每次业务方进行大促期间平台都要进行一次常规压测,做到心里

线程池的创建

package com.newer.cn; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test1 { public static void main(String[] args) { // 创建线程池的方式 // 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在需要时使用提供的 ThreadFactory 创建新线程. E

Java底层技术系列文章-线程池框架

一.线程池结构图    二.示例 定义线程接口 public class MyThread extends Thread { @Override publicvoid run() { System.out.println(Thread.currentThread().getName() + "正在执行"); }}   1:newSingleThreadExecutor ExecutorService pool = Executors. newSingleThreadExecutor()

线程池中的线程的排序问题

1 package org.zln.thread.poolqueue; 2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 6 import java.util.Comparator; 7 import java.util.UUID; 8 import java.util.concurrent.*; 9 10 /** 11 * 线程池中的线程的排序问题 12 * Created by sherry on 16/11/4

多线程篇七:通过Callable和Future获取线程池中单个务完成后的结果

使用场景:如果需要拿到线程的结果,或者在线程完成后做其他操作,可以使用Callable 和 Futrue 1.定义一个线程池,向线程池中提交单个callable任务 ExecutorService threadPools=Executors.newSingleThreadExecutor(); Future<String> future=threadPools.submit(new Callable<String>() { @Override public String call(

多线程篇六:线程池

1.固定大小的线程池 ExecutorService threadPools1=Executors.newFixedThreadPool(3); for(int i=1;i<=10;i++){ final int task=i; //循环10次,一共往线程池里面放10个任务 threadPools1.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().ge