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 ...
随机推荐
- Sql2008 全文索引 简明教程
在SQL Server 中提供了一种名为全文索引的技术,可以大大提高从长字符串里搜索数 据的速度,不用在用LIKE这样低效率的模糊查询了. 下面简明的介绍如何使用Sql2008 全文索引 一.检查 ...
- 一次使用 Redis 优化查询性能的实践
因为我的个人网站 restran.net 已经启用,博客园的内容已经不再更新.请访问我的个人网站获取这篇文章的最新内容,一次使用 Redis 优化查询性能的实践 应用背景 有一个应用需要上传一组ID到 ...
- jQuery常用属性方法大全 attr(),val()
@@@@属性篇: 写作本篇文章的意义:jQuery的教程千千万,却没有英文版的API讲的系统.到位,一些话用中文翻译过来味道就变了,所以我将英文版的API的一些常用的方法单独提出来放在这里,并用自己的 ...
- Git报错的解决方案汇总
错误1: error: Your local changes to the following files would be overwritten by merge:Please, commit y ...
- 126. Word Ladder II( Queue; BFS)
Given two words (beginWord and endWord), and a dictionary's word list, find all shortest transformat ...
- 基础常用JS函数和语法
100多个基础常用JS函数和语法集合大全 来源:http://www.cnblogs.com/hnyei/p/4605103.html 网站特效离不开脚本,javascript是最常用的脚本语言,我 ...
- 为什么数组没有实现Iterable接口,但可以使用foreach语句遍历
在Java中,对于数组为什么能够使用foreach语句一直感觉很困惑. 对于能够使用foreach语句进行遍历的对象,只有两种情况,其中一种是遍历对象必须实现Iterable接口,实现ierator( ...
- php闭包bindTo方法用法
从手册知道,Closure::bindTo — 复制当前闭包对象,绑定指定的$this对象和类作用域. 创建并返回一个 匿名函数, 它与当前对象的函数体相同.绑定了同样变量,但可以绑定不同的对象,也可 ...
- 查询测试程序中的selectOne和selectList函数
selectOne查询一条记录,如果使用selectOne查询多条记录则抛出异常: org.apache.ibatis.exceptions.TooManyResultsException: Expe ...
- android listView布局等分列
android listView布局4等分列. 必须要加上<RelativeLayout 在外层,不然等分不起作用 <RelativeLayout xmlns:android=" ...