线程池的基本概念
什么是线程池?
- ** .NET Framework的ThreadPool类提供一个线程池**
- “线程池”是可以用来在后台执行多个任务的线程集合
- 线程池通常用于服务器应用程序。 每个传入请求都将分配给线程池中的一个线程,因此可以异步处理请求,而不会占用主线程,也不会延迟后续请求的处理
- 一旦池中的某个线程完成任务,它将返回到等待线程队列中,等待被再次使用。 这种重用使应用程序可以避免为每个任务创建新线程的开销
- 线程池通常具有最大线程数限制。 如果所有线程都繁忙,则额外的任务将放入队列中,直到有线程可用时才能够得到处理
为什么要用线程池?
- 线程是非常消耗资源的,如果我们每次需要子线程来执行任务,就去创建一个新的线程,那当我们执行1千次1万次甚至是100万次的时候,那么电脑的资源消耗就非常严重,甚至承受不了
- 线程池可以避免大量的创建和销毁的开支,具有更好的性能和稳定性
- 开发人员把线程交给系统管理,可以集中精力处理其他任务
前台线程&后台线程
- 前台线程: 当程序运行起来后,主线程已经运行结束了,但是程序却没有停止,子线程依然在运行
- 后台线程: 只要主线程和所有的前台线程执行结束,就算后台线程的任务还没完成,也会强行打断直接退出
- 特点: 后台适用于不太重要的任务,即被中断了也没事的。 前台线程适用于比较重要的任务,必须等任务执行完程序才能关闭
- ThreadPool 线程池中的线程都是 后台线程
线程池的使用
设置线程池大小
- ThreadPool.SetMaxThreads (int workerThreads,int completionPortThreads)
- ThreadPool.SetMinThreads (int workerThreads,int completionPortThreads)
- 参数解析:
- completionPortThreads:线程池中异步 I/O 线程的数目 (I/O 完成线程)
- workerThreads:线程池中辅助线程的数目**(工作线程)**
- 对于以上两个参数的解释,摘自网络其它博客:
-
主要用来完成 输入和输出的工作 ,在这种情况下, 计算机需要I/O设备完成输入和输出的任务。在处理过程中, CPU是不需要参与处理过程的 , 此时正在运行的线程将处于等待状态 , 只有等任务完成后才会有事可做 , 这样就造成线程资源浪费的问题。为了解决这样的问题,可以通过线程池来解决这样的问题,让线程池来管理线程
-
.NET中的一些API方法,通过APM(异步编程模式),内部实现了ThreadPool.BindHandle方法。BeginXXX方法将用户的回调委托送到某个设备驱动程序,然后返回线程池。
当做完成后,OS会通过IOCP提醒CLR它工作已经完成,当接收到通知后,I/O线程会醒来并且运行用户的回调
-
所以工作线程由开发人员调用, I/O线程由CLR调用 。所以通常情况下,开发者并不会直接用到它。因此可以认为, 工作者线程和I/O线程没有区别,它们都是普通的线程 , 但是CLR线程池中区分它们的目的是为了避免线程都去处理I/O回调而被耗尽,从而引发死锁 。(设想,所有的工作者线程每一个都去等待I/O异步完成。)
-
用来完成一些 计算的任务 ,在任务执行的过程中,需要 CPU不间断地处理 ,所以,在工作者线程的执行过程中,CPU和线程的资源是充分利用的
-
.NET中的术语工作者线程指的是任何线程而不是仅仅主线程
-
工作者线程
-
I/O线程
-
启动线程任务使用: QueueUserWorkItem()方法
class ThreadPoolTest
{
static void Main()
{
Console.WriteLine("启动多线程...");
for(int i = 0; i <10; i++)
{
ThreadPool.QueueUserWorkItem( p => printStr("当前线程") );
}
Console.WriteLine("结束多线程...");
Console.ReadKey();
}
private static void printStr(string str)
{
Console.WriteLine("{0}是:{1}", str, Thread.CurrentThread.ManagedThreadId);
}
}
输出结果, 可以看到有很多线程ID是重复的,这就是线程池的强大之处了
启动多线程...
结束多线程...
当前线程是:8
当前线程是:6
当前线程是:10
当前线程是:9
当前线程是:7
当前线程是:11
当前线程是:12
当前线程是:9
当前线程是:6
当前线程是:10
查看最大/最小线程数 && 设置最大/最小线程数
class ThreadPoolTest
{
static void Main()
{
// 获取默认的线程池中的最大,最小线程数
ThreadPool.GetMaxThreads(out int maxWorkerThreads, out int maxCompletionPortThreads);
Console.WriteLine("最大线程数,工作线程:{0}, IO线程数:{1}", maxWorkerThreads, maxCompletionPortThreads);
ThreadPool.GetMinThreads(out int minWorkerThreads, out int minCompletionPortThreads);
Console.WriteLine("最小线程数,工作线程:{0}, IO线程数:{1}", minWorkerThreads, minCompletionPortThreads);
// 重新设置最大、最小线程数
ThreadPool.SetMaxThreads(10, 10);
ThreadPool.SetMinThreads(3, 3);
// 获取默认的线程池中的最大,最小线程数
ThreadPool.GetMaxThreads(out int newMaxWorkThread, out int newMaxIOThread);
Console.WriteLine("重新设置后的最大线程数,工作线程:{0}, IO线程数:{1}", newMaxWorkThread, newMaxIOThread);
ThreadPool.GetMinThreads(out int newMinWorkThread, out int newMinIOThread);
Console.WriteLine("重新设置后的最小线程数,工作线程:{0}, IO线程数:{1}", newMinWorkThread, newMinIOThread);
Console.WriteLine("启动多线程...");
for(int i = 0; i <10; i++)
{
ThreadPool.QueueUserWorkItem( p => printStr("当前线程") );
}
Console.WriteLine("结束多线程...");
Console.ReadKey();
}
private static void printStr(string str)
{
Console.WriteLine("{0}是:{1}", str, Thread.CurrentThread.ManagedThreadId);
}
}
输出结果
最大线程数,工作线程:32767, IO线程数:1000
最小线程数,工作线程:8, IO线程数:8
重新设置后的最大线程数,工作线程:10, IO线程数:10
重新设置后的最小线程数,工作线程:3, IO线程数:3
启动多线程...
结束多线程...
当前线程是:6
当前线程是:9
当前线程是:7
当前线程是:8
当前线程是:11
当前线程是:10
当前线程是:12
当前线程是:11
当前线程是:7
当前线程是:9
ThreadPool.SetMaxThreads的默认值
- 它取决于.NET框架版本,在2.0,3.0和4.0中进行了更改。 在2.0中它是核心数量的50倍。 在3.0(又名2.0 SP1)中,它是内核数量的250倍,4.0根据位数和操作系统资源使其动态化。 默认 Max I / O完成线程是1000
- 一般来说,它是非常的高,一个程序永远不会接近
使用以上方法设置线程数据大小时需注意
- 不能将最大工作线程数或 I/O 完成线程数设置为小于计算机上的处理器数的数字
- 不能将最大工作线程数或 I/O 完成线程数设置为小于相应最小工作线程数或 I/O 完成线程数的数字
- 默认情况下,最小线程数设置为系统上的处理器数。 可以使用该方法 SetMinThreads 增加最小线程数。 但是,不必要地增加这些值可能导致性能问题。 如果在同一时间开始太多的任务,则所有任务均可能会很慢。 在大多数情况下,线程池将使用自己的算法更好地分配线程。 将最小处理器减少到小于处理器数也会损害性能
查看系统cpu的处理数
编辑
**线程等待 **
由于线程池中的线程都是后台线程,当主线程及所有前台线程执行完时,后台线程就会被中断,但如果我们希望 主线程等待 线程池执行完成任务后再关闭程序 怎么办呢?
需要使用到ManualResetEvent类
编辑
**ManualResetEvent需要一个bool类型的参数来表示暂停和停止
**
class ThreadPoolTest2
{
// 参数设置为false
static ManualResetEvent manualResetEvent = new ManualResetEvent(false);
static void Main()
{
Console.WriteLine("启动多线程...");
ThreadPool.QueueUserWorkItem(p => printStr("当前线程"));
// 等待 线程池执行任务完成
manualResetEvent.WaitOne();
Console.WriteLine("结束多线程...");
}
private static void printStr(string str)
{
Console.WriteLine("{0}是:{1}", str, Thread.CurrentThread.ManagedThreadId);
// 设置为true
manualResetEvent.Set();
}
}
输出结果
启动多线程...
当前线程是:6
结束多线程...
ManualResetEvent类的参数值执行顺序如下:
- false-- WaitOne等待
- true--WaitOne通过
注:一般情况下,不要阻塞线程池中的线程,因为写代码无意间造成的线程等待没有释放,一旦线程池线程耗尽就会形成死锁
造成死锁的案例: 设置了最大线程数是9,循环创建15个线程,意味着 后6个线程必然需要利用线程池中前面用过的线程, 但是由于前面创建的线程都直接让阻塞了,没有释放,这时循环到第10个线程时由于没有空闲线程,线程池没法执行就直接跳过了,主线程会继续执行后面的循环,这样也永远不会 执行 manualResetEvent.Set() 方法,最后就成死锁了
private static void Test1()
{
// 设置最大线程
ThreadPool.SetMaxThreads(9, 9);
// 设置最小线程
ThreadPool.SetMinThreads(3, 3);
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
for (int i = 0; i < 15; i++)
{
int k = i;
ThreadPool.QueueUserWorkItem(p =>
{
Console.WriteLine(k);
if (k < 10)
{
manualResetEvent.WaitOne();
}
else
{
// 设为true
manualResetEvent.Set();
}
});
}
if (manualResetEvent.WaitOne())
{
Console.WriteLine("没有死锁。。。。。。。。。");
}
Console.WriteLine("结束。。。。。。。。。。");
}
输出结果
1
0
2
3
4
5
6
7
8
-
服务器
+关注
关注
12文章
9138浏览量
85373 -
应用程序
+关注
关注
37文章
3267浏览量
57693 -
线程池
+关注
关注
0文章
57浏览量
6846
发布评论请先 登录
相关推荐
评论