c# 多线程线程池基础
线程池的作用
在上一篇中我们了解了创建和销毁线程是一个昂贵的操作,要耗费大量的时间,太多的线程会浪费内存资源,当线程数量操作计算机CPU的数量后操作系统必须调度可运行的线程并执行上下文切换,所有太多的线程还会影响性能,那么有没有办法让线程可以重复使用了,让线程干完活之后不用销毁,把它放在一个容器中, 等待下次有任务的时候在从容器中取出来就行了,这样就避免了创建和销毁所带来的性能损耗,所有线程池的作用总结起来就是:因为创建一个线程的代价较高,因此我们使用线程池设法复用线程。
线程基础
每个CLR 拥有一个线程池,这个线程池由CLR控制的APPDomain 共享。在线程池内部,它自己维护着一个操作请求队列,应用程序需要执行某个任务时,就需要调用线程池的一个方法(通常是QueueUserWorkItem 方法)将任务添加到线程池工作项中,线程池就会将任务分派给一个线程池线程处理,如果线程池中没有线程,就会创建一个线程来处理这个任务。当任务执行完成以后,这个线程会回到线程池中处于空闲状态,等待下一个执行任务。由于线程不会销毁,所以使用线程池线程在执行任务的速度上会更快。
如果线程池中的任务过多超过了现有线程的处理能力时,线程池就会根据需要在创建更多的线程。由于每个线程都要占用一定的内存资源,所以当线程池空闲线程(长时间不执行任务的线程)过多时,线程池中线程会自动醒来销毁多余的空闲线程,以减少资源的使用。
在线程池内部,所有线程都是后台线程并且调度优先级都为普通(ThreadPriority.Normal),这些线程分为工作者或I/O线程,当线程池线程执行的任务是一个复杂的计算任务时,使用的就是工作者线程。如果执行的任务与I/O相关,就会使用I/O线程。
如何使用线程池
ThreadPool 类是一个静态类型类,使用ThreadPool 类执行异步时通常调用ThreadPool 的 QueueUserWorkItem 方法,这个方法有一个重载版本,如下
public static bool QueueUserWorkItem(WaitCallback callBack)
{
StackCrawlMark stackCrawlMark = StackCrawlMark.LookForMyCaller;
return ThreadPool.QueueUserWorkItemHelper(callBack, null, ref stackCrawlMark, true);
}
public static bool QueueUserWorkItem(WaitCallback callBack, object state)
{
StackCrawlMark stackCrawlMark = StackCrawlMark.LookForMyCaller;
return ThreadPool.QueueUserWorkItemHelper(callBack, state, ref stackCrawlMark, true);
}
QueueUserWorkItem 方法接受一个WaitCallback 类型的委托作为回调方法以及可以选择传递一个线程池线程执行回调方法时所需要的数据对象。
WaitCallback 委托类型的定义如下:
namespace System.Threading
{
[__DynamicallyInvokable, ComVisible(true)]
public delegate void WaitCallback(object state);
}
线程池的QueueUserWorkItem方法在调用以后会立即返回,所传递的回调方法会有以后线程池线程执行。使用线程池线程执行异步任务代码如下:
static void Main(string[] args)
{ Console.WriteLine("主线程开始执行任务,线程ID:{0}",Thread.CurrentThread.ManagedThreadId);
//使用 ThreadPool.QueueUserWorkItem 方法将一个异步任务添加到线程池任务队列中,
//可以为线程池线程执行方法时传递一个数据对象,
//如果不需要传递数据可以使用QueueUserWorkItem只有WaitCallback一个参数类型的版本,
//或传递null
ThreadPool.QueueUserWorkItem(ThreadMethod,null);
Console.WriteLine("主线程执行其他任务。线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
//使调用线程睡眠2000毫秒,等待线程池线程执行完成。
Thread.Sleep(2000); Console.WriteLine("主线程继续执行任务。线程ID:{0}", Thread.CurrentThread.ManagedThreadId); }
public static void ThreadMethod(object state)
{
Console.WriteLine("我是由线程池创建的线程,线程ID:{0}",Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < 10; i++)
{
Thread.Sleep(200);
Console.WriteLine(i);
}
}
输出
主线程开始执行任务,线程ID:1
主线程执行其他任务。线程ID:1
我是由线程池创建的线程,线程ID:3
0
1
2
3
4
5
6
7
8
主线程继续执行任务。线程ID:1
请按任意键继续. . .
线程池对线程的管理
线程池中的线程是由工作者线程和I/O线程组成的, CLR允许开发人员设置线程池需要创建的最大线程数量,但实践证明,线程池永远都不应该为池中的线程数设置上限,因为可能发生饥饿或死锁,假定队列中有1000个工作项,但这些工作项全都因为一个事件而阻塞,等第1001个工作项发出信号才能解除阻塞,如果设置了最大1000个线程,第1001个工作项就不会执行,所有1000个线程都会一直阻塞,最终用户将被终止应用程序,并丢失他们的所有未保存的工作。
//设置可以同时处于活动状态的线程池的请求数目。
// 所有大于此数目的请求将保持排队状态,直到线程池线程变为可用。
public static bool SetMaxThreads(int workerThreads, int completionPortThreads);
// 发出新的请求时,在切换到管理线程创建和销毁的算法之前设置线程池按需创建的线程的最小数量。
public static bool SetMinThreads(int workerThreads, int completionPortThreads);
//检索可以同时处于活动状态的线程池请求的数目。 所有大于此数目的请求将保持排队状态,直到线程池线程变为可用。
public static void GetMaxThreads(out int workerThreads, out int completionPortThreads);
//发出新的请求时,在切换到管理线程创建和销毁的算法之前检索线程池按需创建的线程的最小数量。
public static void GetMinThreads(out int workerThreads, out int completionPortThreads);
//获得最大线程池线程数和当前运行线程数之间的差值。
public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads);
虽然ThreadPool类提供了上诉方法,但建议不要调用上诉中的方法,限制线程池的线程数,一般都只造成应用程序的性能变的更差,而不会更好,使用方法
static void Main(string[] args)
{
int worker, io;
//获得线程池默认最大线程数
ThreadPool.GetMaxThreads(out worker, out io);
Console.WriteLine("1、CLR线程池默认最大线程数据,工作者线程数:{0},IO线程数:{1}", worker, io);
//设置线程池最大线程数
ThreadPool.SetMaxThreads(100, 100);
ThreadPool.QueueUserWorkItem(state =>
{
Thread.Sleep(2000);
Console.WriteLine("4、线程池线程开始执行异步任务。线程ID:{0}", Thread.CurrentThread.ManagedThreadId); });
Console.WriteLine("2、自定义设置线程池默认最大线程数据后,工作者线程数:{0},IO线程数:{1}", worker, io);
//获得最大线程池线程数和当前运行线程数之间的差值。
ThreadPool.GetAvailableThreads(out worker, out io);
Console.WriteLine("3、获得最大线程池线程数和当前运行线程数之间的差值,工作者线程:{0},IO线程:{1}", worker, io);
Console.Read(); } 1、CLR线程池默认最大线程数据,工作者线程数:2047,IO线程数:1000
2、自定义设置线程池默认最大线程数据后,工作者线程数:2047,IO线程数:1000
3、获得最大线程池线程数和当前运行线程数之间的差值,工作者线程:99,IO线程:100
4、线程池线程开始执行异步任务。线程ID:3
c# 多线程线程池基础的更多相关文章
- C#多线程--线程池(ThreadPool)
先引入一下线程池的概念: 百度百科:线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的堆栈大小,以默认的优先级运行, ...
- linux C 多线程/线程池编程 同步实例
在多线程.线程池编程中经常会遇到同步的问题. 1.创建线程 函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, ...
- Java 多线程(五)—— 线程池基础 之 FutureTask源码解析
FutureTask是一个支持取消行为的异步任务执行器.该类实现了Future接口的方法. 如: 取消任务执行 查询任务是否执行完成 获取任务执行结果(”get“任务必须得执行完成才能获取结果,否则会 ...
- Java 基础 多线程和线程池基础
一,多线程 1.1 多线程介绍 进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线程:线程是进程中的一个执行单元,负 ...
- Java线程和多线程(十二)——线程池基础
Java 线程池管理多个工作线程,其中包含了一个队列,包含着所有等待被执行的任务.开发者可以通过使用ThreadPoolExecutor来在Java中创建线程池. 线程池是Java中多线程的一个重要概 ...
- java基础-多线程线程池
线程池 * 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互.而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池.线程池里的每一个线程代 ...
- JAVA基础知识之多线程——线程池
线程池概念 操作系统或者JVM创建一个线程以及销毁一个线程都需要消耗CPU资源,如果创建或者销毁线程的消耗源远远小于执行一个线程的消耗,则可以忽略不计,但是基本相等或者大于执行线程的消耗,而且需要创建 ...
- C#线程池基础
池(Pool)是一个很常见的提高性能的方式.比如线程池连接池等,之所以有这些池是因 为线程和数据库连接的创建和关闭是一种比较昂贵的行为.对于这种昂贵的资源我们往往会考虑在一个池容器中放置一些资源,在用 ...
- delphi 线程池基础 TSimplePool
1. TSimpleThread 2. TSimpleList 3. 以1,2构成 TSimplePool 用法 先定义: TDoSomeThingThread=class(TSimpleThread ...
随机推荐
- hdu-4432-Sum of divisors
/* Sum of divisors Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) ...
- mybatis一对多关联查询+pagehelper->分页错误
mybatis一对多关联查询+pagehelper->分页错误. 现象: 网上其他人遇到的类似问题:https://segmentfault.com/q/1010000009692585 解决: ...
- 1.ehcache实现页面整体缓存和页面局部缓存
转自:https://www.cnblogs.com/jianjianyang/p/4933016.html 好长时间没写博客了,真的是没时间啊.ps:其实就是懒!!接下来几篇要写下缓存,这里主要写下 ...
- Spring cloud @RefreshScope使用
参数 @RestController @RefreshScope public class HomeController { @Value("${foo}") String foo ...
- vs启动项目提示Web 服务器被配置为不列出此目录的内容。
解决方法 确认网站或应用程序配置文件中的 configuration/system.webServer/directoryBrowse@enabled 属性已设置为 true. 配置一下web.con ...
- 在VMware Workstation中安装Ubuntu Server 16.04.5图解教程
最近要在Ubuntu中部署openstack,为了节省空间和内存,最终选择了Ubuntu服务器.看了很多前辈和大佬的安装教程,在这里记录一下我自己的Ubuntu Server 16.04.5的安装过程 ...
- linux 一个网卡配置多个IP
在Redhat系列(redhat,Fedora,Centos,Gentoo)中的实现方法如下: 1.单网卡绑定多IP在Redhat系列中的实现方法 假设需要绑定多IP的网卡是eth0,请在/etc/s ...
- golang apns升级到http2
记录一下golang中升级apns,使用http2替换http1.1的详细过程. apns使用http2的好处就不用再说了,网上一搜一堆信息.苹果的apns推送在2015年8月就支持了http2协议, ...
- NLTK和Stanford NLP两个工具的安装配置
这里安装的是两个自然语言处理工具,NLTK和Stanford NLP. 声明:笔者操作系统是Windows10,理论上Windows都可以: 版本号:NLTK 3.2 Stanford NLP 3.6 ...
- [Selenium]Grid模式下运行时打印出当前Case在哪台node机器上运行
当Case在本地运行成功,在Grid模式下运行失败时,我们需要在Grid模式下进行调试,同时登录远程的node去查看运行的情况. Hub是随机将case分配到某台node上运行的,怎样知道当前的cas ...