线程池之ThreadPool类与辅助线程 - <第二篇>
一、CLR线程池
管理线程开销最好的方式:
- 尽量少的创建线程并且能将线程反复利用(线程池初始化时没有线程,有程序请求线程则创建线程);
- 最好不要销毁而是挂起线程达到避免性能损失(线程池创建的线程完成任务后以挂起状态回到线程池中,等待下次请求);
- 通过一个技术达到让应用程序一个个执行工作,类似于一个队列(多个应用程序请求线程池,线程池会将各个应用程序排队处理);
- 如果某一线程长时间挂起而不工作的话,需要彻底销毁并且释放资源(线程池自动监控长时间不工作的线程,自动销毁);
- 如果线程不够用的话能够创建线程,并且用户可以自己定制最大线程创建的数量(当队列过长,线程池里的线程不够用时,线程池不会坐视不理);
微软早就替我们想到了,为我们实现了线程池。
CLR线程池并不会在CLR初始化时立即建立线程,而是在应用程序要创建线程来运行任务时,线程池才初始化一个线程。
线程池初始化时是没有线程的,线程池里的。线程的初始化与其他线程一样,但是在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池。直到应用程序再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。
这样既节省了建立线程所造成的性能损耗,也可以让多个任务反复重用同一线程,从而在应用程序生存期内节约大量开销。
通过CLR线程池所建立的线程总是默认为后台线程,优先级数为ThreadPriority.Normal。
二、工作者线程与I/O线程
CLR线程池分为工作者线程(workerThreads)与I/O线程(completionPortThreads)两种:
- 工作者线程是主要用作管理CLR内部对象的运作,通常用于计算密集的任务。
- I/O(Input/Output)线程主要用于与外部系统交互信息,如输入输出,CPU仅需在任务开始的时候,将任务的参数传递给设备,然后启动硬件设备即可。等任务完成的时候,CPU收到一个通知,一般来说是一个硬件的中断信号,此时CPU继续后继的处理工作。在处理过程中,CPU是不必完全参与处理过程的,如果正在运行的线程不交出CPU的控制权,那么线程也只能处于等待状态,即使操作系统将当前的CPU调度给其他线程,此时线程所占用的空间还是被占用,而并没有CPU处理这个线程,可能出现线程资源浪费的问题。如果这是一个网络服务程序,每一个网络连接都使用一个线程管理,可能出现大量线程都在等待网络通信,随着网络连接的不断增加,处于等待状态的线程将会很消耗尽所有的内存资源。可以考虑使用线程池解决这个问题。
线程池的最大值一般默认为1000、2000。当大于此数目的请求时,将保持排队状态,直到线程池里有线程可用。
使用CLR线程池的工作者线程一般有两种方式:
- 通过ThreadPool.QueueUserWorkItem()方法;
- 通过委托;
要注意,不论是通过ThreadPool.QueueUserWorkItem()还是委托,调用的都是线程池里的线程。
三、ThreadPool类常用方法
通过以下两个方法可以读取和设置CLR线程池中工作者线程与I/O线程的最大线程数。
- ThreadPool.GetMax(out in workerThreads,out int completionPortThreads);
- ThreadPool.SetMax(int workerThreads,int completionPortThreads);
若想测试线程池中有多少线程正在投入使用,可以通过ThreadPool.GetAvailableThreads(out in workThreads,out int conoletionPortThreads)方法。
方法 | 说明 |
GetAvailableThreads | 剩余空闲线程数 |
GetMaxThreads | 最多可用线程数,所有大于此数目的请求将保持排队状态,直到线程池线程变为可用 |
GetMinThreads | 检索线程池在新请求预测中维护的空闲线程数。 |
QueueUserWorkItem | 启动线程池里得一个线程(队列的方式,如线程池暂时没空闲线程,则进入队列排队) |
SetMaxThreads | 设置线程池中的最大线程数 |
SetMinThreads | 设置线程池最少需要保留的线程数 |
class Program
{
static void Main(string[] args)
{
int i = 0;
int j = 0;
//前面是辅助(也就是所谓的工作者)线程,后面是I/O线程
ThreadPool.GetMaxThreads(out i, out j);
Console.WriteLine(i.ToString() + " " + j.ToString()); //默认都是1000 //获取空闲线程,由于现在没有使用异步线程,所以为空
ThreadPool.GetAvailableThreads(out i, out j);
Console.WriteLine(i.ToString() + " " + j.ToString()); //默认都是1000 Console.ReadKey();
}
}
四、各种调用线程池线程的方法
1、通过QueueUserWorkItem启动工作者线程
ThreadPool线程池中有两个重载的静态方法可以直接启动工作者线程:
- ThreadPool.QueueUserWorkItem(waitCallback);
- ThreadPool.QueueUserWorkItem(waitCallback,Object);
先把WaitCallback委托指向一个带有Object参数的无返回值方法,再使用ThreadPool.QueueUserWorkItem(WaitCallback)就可以一步启动此方法,此时异步方法的参数被视为null。
下面来试下用QueueUserWorkItem启动线程池里的一个线程。注意哦,由于是一直存在于线程池,所以不用new Thread()。
class Program
{
static void Main(string[] args)
{
//工作者线程最大数目,I/O线程的最大数目
ThreadPool.SetMaxThreads(1000, 1000);
//启动工作者线程
ThreadPool.QueueUserWorkItem(new WaitCallback(RunWorkerThread)); Console.ReadKey();
} static void RunWorkerThread(object state)
{
Console.WriteLine("RunWorkerThread开始工作");
Console.WriteLine("工作者线程启动成功!");
}
}
输出:
使用第二个重载方法ThreadPool.QueueUserWorkItem(WaitCallback,object)方法可以把object对象作为参数传送到回调函数中。
class Program
{
static void Main(string[] args)
{
Person p = new Person(1,"刘备");
//启动工作者线程
ThreadPool.QueueUserWorkItem(new WaitCallback(RunWorkerThread), p);
Console.ReadKey();
} static void RunWorkerThread(object obj)
{
Thread.Sleep(200);
Console.WriteLine("线程池线程开始!");
Person p = obj as Person;
Console.WriteLine(p.Name);
}
} public class Person
{
public Person(int id,string name) { Id = id; Name = name; }
public int Id { get; set; }
public string Name { get; set; }
}
输出结果如下:
通过ThreadPool.QueueUserWork启动工作者线程非常方便,但是WaitCallback委托指向的必须是一个带有object参数的无返回值方法。所以这个方法启动的工作者线程仅仅适合于带单个参数和无返回值的情况。
那么如果要传递多个参数和要有返回值又应该怎么办呢?那就只有通过委托了。
2、BeginInvoke与EndInvoke委托异步调用线程
异步调用委托的步骤如下:
- 建立一个委托对象,通过IAsyncResult BeginInvoke(string name,AsyncCallback callback,object state)异步调用委托方法,BeginInvoke方法除最后的两个参数外,其他参数都是与方法参数相对应的。
- 利用EndInvoke(IAsyncResult--上一步BeginInvoke返回的对象)方法就可以结束异步操作,获取委托的运行结果。
class Program
{
//除了最后两个参数,前面的都是你可定义的
delegate string MyDelegate(string name,int age);
static void Main(string[] args)
{
//建立委托
MyDelegate myDelegate = new MyDelegate(GetString);
//异步调用委托,除最后两个参数外,前面的参数都可以传进去
IAsyncResult result = myDelegate.BeginInvoke("刘备",22, null, null); //IAsynResult还能轮询判断,功能不弱 Console.WriteLine("主线程继续工作!"); //调用EndInvoke(IAsyncResult)获取运行结果,一旦调用了EndInvoke,即使结果还没来得及返回,主线程也阻塞等待了
//注意获取返回值的方式
string data = myDelegate.EndInvoke(result);
Console.WriteLine(data); Console.ReadKey();
} static string GetString(string name, int age)
{
Console.WriteLine("我是不是线程池线程" + Thread.CurrentThread.IsThreadPoolThread);
Thread.Sleep(2000);
return string.Format("我是{0},今年{1}岁!",name,age);
}
}
输出如下:
这种方法有一个缺点,就是不知道异步操作什么时候执行完,什么时候开始调用EndInvoke,因为一旦EndInvoke主线程就会处于阻塞等待状态。
3、IAsyncResult轮询
为了克服上面提到的缺点,此时可以好好利用IAsyncResult提高主线程的工作性能,IAsyncResult有如下成员。
public interface IAsyncResult
{
object AsyncState {get;} //获取用户定义的对象,它限定或包含关于异步操作的信息。
WailHandle AsyncWaitHandle {get;} //获取用于等待异步操作完成的 WaitHandle。
bool CompletedSynchronously {get;} //获取异步操作是否同步完成的指示。
bool IsCompleted {get;} //获取异步操作是否已完成的指示。
}
示例如下:
class Program
{
delegate string MyDelegate(string name,int age);
static void Main(string[] args)
{
MyDelegate myDelegate = new MyDelegate(GetString);
IAsyncResult result = myDelegate.BeginInvoke("刘备",22, null, null); Console.WriteLine("主线程继续工作!"); //比上个例子,只是利用多了一个IsCompleted属性,来判断异步线程是否完成
while (!result.IsCompleted)
{
Thread.Sleep(500);
Console.WriteLine("异步线程还没完成,主线程干其他事!");
} string data = myDelegate.EndInvoke(result);
Console.WriteLine(data); Console.ReadKey();
} static string GetString(string name, int age)
{
Thread.Sleep(2000);
return string.Format("我是{0},今年{1}岁!",name,age);
}
}
输出如下:
以上例子,除了IsCompleted属性外,还可以使用AsyncWaitHandle如下3个方法实现同样轮询判断效果:
- WaitOne:判断单个异步线程是否完成;
- WaitAny:判断是否异步线程是否有指定数量个已完成;
- WaitAll:判断是否所有的异步线程已完成;
WaitOne:
//比上个例子,判断条件由IsCompleted属性换成了AsyncWaitHandle,仅此而已
while (!result.AsyncWaitHandle.WaitOne(200))
{
Console.WriteLine("异步线程没完,主线程继续干活!");
}
WaitAny:
//是否完成了指定数量
WaitHandle[] waitHandleList = new WaitHandle[] { result.AsyncWaitHandle };
while (WaitHandle.WaitAny(waitHandleList, 200) > 0)
{
Console.WriteLine("异步线程完成数未大于0,主线程继续甘其他事!");
}
WaitAll:
WaitHandle[] waitHandleList = new WaitHandle[] { result.AsyncWaitHandle };
//是否全部异步线程完成
while (!WaitHandle.WaitAll(waitHandleList, 200))
{
Console.WriteLine("异步线程未全部完成,主线程继续干其他事!");
}
4、IAsyncResult回调函数
使用轮询方式来检测异步方法的状态非常麻烦,而且影响了主线程,效率不高。能不能异步线程完成了就直接调用实现定义好的处理函数呢?
有,还是强大的IAsyncResult对象。
class Program
{
delegate string MyDelegate(string name, int age); static void Main(string[] args)
{
//建立委托
MyDelegate myDelegate = new MyDelegate(GetString);
//倒数第二个参数,委托中绑定了完成后的回调方法
IAsyncResult result1 = myDelegate.BeginInvoke("刘备",23, new AsyncCallback(Completed), null);
//主线程可以继续工作而不需要等待
Console.WriteLine("我是主线程,我干我的活,不再理你!");
Thread.Sleep(5000);
//Console.ReadKey();
} static string GetString(string name, int age)
{
Thread.CurrentThread.Name = "异步线程";
//注意,如果不设置为前台线程,则主线程完成后就直接卸载程序了
//Thread.CurrentThread.IsBackground = false;
Thread.Sleep(2000);
return string.Format("我是{0},今年{1}岁!", name, age);
} //供异步线程完成回调的方法
static void Completed(IAsyncResult result)
{
//获取委托对象,调用EndInvoke方法获取运行结果
AsyncResult _result = (AsyncResult)result;
MyDelegate myDelegaate = (MyDelegate)_result.AsyncDelegate;
//获得参数
string data = myDelegaate.EndInvoke(_result);
Console.WriteLine(data);
//异步线程执行完毕
Console.WriteLine("异步线程完成咯!");
Console.WriteLine("回调函数也是由" + Thread.CurrentThread.Name + "调用的!");
}
}
输出如下:
注意:
- 回调函数依然是在辅助线程中执行的,这样就不会影响主线程的运行。
- 线程池的线程默认是后台线程。但是如果主线程比辅助线程优先完成,那么程序已经卸载,回调函数未必会执行。如果不希望丢失回调函数中的操作,要么把异步线程设为前台线程,要么确保主线程将比辅助线程迟完成。
到目前为止,BeginInvoke("刘备",23, new AsyncCallback(Completed), null)还有最后一个参数没用过的。那么最后一个参数是用来干什么?传参:
namespace 控制台___学习测试
{
class Program
{
delegate string MyDelegate(string name, int age); static void Main(string[] args)
{
Person p = new Person(2,"关羽"); //建立委托
MyDelegate myDelegate = new MyDelegate(GetString);
//最后一个参数的作用,原来是用来传参的
IAsyncResult result1 = myDelegate.BeginInvoke("刘备", 23, new AsyncCallback(Completed), p);
//主线程可以继续工作而不需要等待
Console.WriteLine("我是主线程,我干我的活,不再理你!");
Console.ReadKey();
} static string GetString(string name, int age)
{
Thread.CurrentThread.Name = "异步线程";
//注意,如果不设置为前台线程,则主线程完成后就直接卸载程序了
Thread.CurrentThread.IsBackground = false;
Thread.Sleep(2000);
return string.Format("我是{0},今年{1}岁!", name, age);
} //供异步线程完成回调的方法
static void Completed(IAsyncResult result)
{
//获取委托对象,调用EndInvoke方法获取运行结果
AsyncResult _result = (AsyncResult)result;
MyDelegate myDelegaate = (MyDelegate)_result.AsyncDelegate;
//获得参数
string data = myDelegaate.EndInvoke(_result);
Console.WriteLine(data); Person p = result.AsyncState as Person;
Console.WriteLine("传过来的参数是:" + p.Name);
//异步线程执行完毕
Console.WriteLine("异步线程完成咯!");
Console.WriteLine("回调函数也是由" + Thread.CurrentThread.Name + "调用的!");
}
} public class Person
{
public Person(int id, string name)
{
Id = id;
Name = name;
} public int Id
{
get;
set;
} public string Name
{
get;
set;
}
}
}
输出如下:
线程池之ThreadPool类与辅助线程 - <第二篇>的更多相关文章
- 转载 线程池之ThreadPool类与辅助线程 - <第二篇>
http://www.cnblogs.com/kissdodog/archive/2013/03/28/2986026.html 一.CLR线程池 管理线程开销最好的方式: 尽量少的创建线程并且能将线 ...
- C#多线程--线程池(ThreadPool)
先引入一下线程池的概念: 百度百科:线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的堆栈大小,以默认的优先级运行, ...
- Java线程池(ThreadPool)详解
线程五个状态(生命周期): 线程运行时间 假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间. 如果:T1 + T3 远大于 T2,则可以 ...
- 高并发之——不得不说的线程池与ThreadPoolExecutor类浅析
一.抛砖引玉 既然Java中支持以多线程的方式来执行相应的任务,但为什么在JDK1.5中又提供了线程池技术呢?这个问题大家自行脑补,多动脑,肯定没坏处,哈哈哈... 说起Java中的线程池技术,在很多 ...
- 【高并发】不得不说的线程池与ThreadPoolExecutor类浅析
大家好,我是冰河~~ 今天,我们一起来简单聊聊线程池中的ThreadPoolExecutor类,好了,不多说了,开始进入今天的正题. 一.抛砖引玉 既然Java中支持以多线程的方式来执行相应的任务,但 ...
- 线程池(ThreadPool)
线程池概述 由系统维护的容纳线程的容器,由CLR控制的所有AppDomain共享.线程池可用于执行任务.发送工作项.处理异步 I/O.代表其他线程等待以及处理计时器. 线程池与线程 性能:每开启一个新 ...
- python线程池(threadpool)
一.安装 pip install threadpool 二.使用介绍 (1)引入threadpool模块 (2)定义线程函数 (3)创建线程 池threadpool.ThreadPool() (4)创 ...
- 隔离技术线程池(ThreadPool)和信号量(semaphore)
一.首先要明白Semaphore和线程池各自是干什么? 信号量Semaphore是一个并发工具类,用来控制可同时并发的线程数,其内部维护了一组虚拟许可,通过构造器指定许可的数量,每次线程执行操作时先通 ...
- python 进程池(multiprocessing.Pool)和线程池(threadpool.ThreadPool)的区别与实例
一般我们是通过动态创建子进程(或子线程)来实现并发服务器的,但是会存在这样一些缺点: 1.动态创建进程(或线程)比较耗费时间,这将导致较慢的服务器响应. 2.动态创建的子进程通常只用来为一个客户服务 ...
随机推荐
- 【温故而知新:文件操作】C#的文件读写相关
StreamReader类以及其方法ReadLine,Read,ReadToEnd的分析 首先StreamReader类的构造参数非常丰富在这里,我觉得最常用的就是StreamReader(Strea ...
- python中的继承原则
继承是面向对象的重要特征之一,继承是两个类或者多个类之间的父子关系,子进程继承了父进程的所有公有实例变量和方法.继承实现了代码的重用.重用已经存在的数据和行为,减少代码的重新编写,python在类名 ...
- 可变参数列表-Java SE5新特性(转)
Java1.5增加了新特性:可变参数:适用于参数个数不确定,类型确定的情况,java把可变参数当做数组处理.注意:可变参数必须位于最后一项.当可变参数个数多于一个时,必将有一个不是最后一项,所以只支持 ...
- Fish’s mission
Fish’s mission 也就是求一个坐标到各个食堂的距离和最小,随机化做应该也是可以的.标程用的方法是利用单调性,不断尝试四个方向,二分的方法做的.实际上就是蚁群退火算法. #include & ...
- 杭电1142(最短路径+dfs)
A Walk Through the Forest Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Jav ...
- 方案:解决 wordpress 中 gravatar 头像被墙问题
Gravatar头像具有很好的通用性,但是却遭到了无辜的拦截,对于无法加载头像URL,我们在WordPress系统中通过修改默认的URL链接可以达到恢复头像的功能. 修改文件路径为 /wp-inclu ...
- UVa 147 Dollars(硬币转换)
题目大意:给出五种硬币,价值分别为 1,5,10,25,50,.当给出一个价值时,求出能够组合的种数(每种硬币可以用无限次). 思路:完全背包, dp[i][j]表示总数 i 能够被表示的种数.状态转 ...
- 【剑指offer】面试题30:最小的 k 个数
题目: 输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,. 思路: 这个是O(nlogk)时间复杂度的思路:用一个容器来保存最先 ...
- bst 二叉搜索树简单实现
//数组实现二叉树: // 1.下标为零的元素为根节点,没有父节点 // 2.节点i的左儿子是2*i+1:右儿子2*i+2:父节点(i-1)/2: // 3.下标i为奇数则该节点有有兄弟,否则又左兄弟 ...
- Shortest Word Distance 解答
Question Given a list of words and two words word1 and word2, return the shortest distance between t ...