C# (江湖小新)- 多线程之线程池 (线程也有专门的管理部门)
2023-09-11 14:14:49 时间
线程池的基本概念
什么是线程池?
- .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:线程池中辅助线程的数目(工作线程)
- 对于以上两个参数的解释,摘自网络其它博客:
- 工作者线程
- 用来完成一些计算的任务,在任务执行的过程中,需要CPU不间断地处理,所以,在工作者线程的执行过程中,CPU和线程的资源是充分利用的
- .NET中的术语工作者线程指的是任何线程而不是仅仅主线程
- I/O线程
- 主要用来完成输入和输出的工作,在这种情况下, 计算机需要I/O设备完成输入和输出的任务。在处理过程中,CPU是不需要参与处理过程的,此时正在运行的线程将处于等待状态,只有等任务完成后才会有事可做, 这样就造成线程资源浪费的问题。为了解决这样的问题,可以通过线程池来解决这样的问题,让线程池来管理线程
- .NET中的一些API方法,通过APM(异步编程模式),内部实现了ThreadPool.BindHandle方法。BeginXXX方法将用户的回调委托送到某个设备驱动程序,然后返回线程池。
当做完成后,OS会通过IOCP提醒CLR它工作已经完成,当接收到通知后,I/O线程会醒来并且运行用户的回调 -
所以工作线程由开发人员调用,I/O线程由CLR调用。所以通常情况下,开发者并不会直接用到它。因此可以认为,工作者线程和I/O线程没有区别,它们都是普通的线程,但是CLR线程池中区分它们的目的是为了避免线程都去处理I/O回调而被耗尽,从而引发死锁。(设想,所有的工作者线程每一个都去等待I/O异步完成。)
- 工作者线程
启动线程任务使用: QueueUserWorkItem()方法
-
static bool QueueUserWorkItem(WaitCallback callBack)
:参数为一个带一个object
类型参数的委托,最后返回bool
值成功则返回true
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
更多**好看的内容**和**好玩的案例**请关注**我的微信公众号: 程序猿知秋**
相关文章
- Lua与C#传参
- C# 中一些类关系的判定方法 C#中关于增强类功能的几种方式 Asp.Net Core 轻松学-多线程之取消令牌
- ASP.NET MVC Filters 4种默认过滤器的使用【附示例】 数据库常见死锁原因及处理 .NET源码中的链表 多线程下C#如何保证线程安全? .net实现支付宝在线支付 彻头彻尾理解单例模式与多线程 App.Config详解及读写操作 判断客户端是iOS还是Android,判断是不是在微信浏览器打开
- C# 利用ICSharpCode.SharpZipLib实现在线加密压缩和解密解压缩 C# 文件压缩加解密
- Task C# 多线程和异步模型 TPL模型 【C#】43. TPL基础——Task初步 22 C# 第十八章 TPL 并行编程 TPL 和传统 .NET 异步编程一 Task.Delay() 和 Thread.Sleep() 区别
- C#字符串数组排序 C#排序算法大全 C#字符串比较方法 一个.NET通用JSON解析/构建类的实现(c#) C#处理Json文件 asp.net使用Jquery+iframe传值问题
- 装饰者模式的学习(c#) EF SaveChanges() 报错(转载) C# 四舍五入 保留两位小数(转载) DataGridView样式生成器使用说明 MSSQL如何将查询结果拼接成字符串 快递查询 C# 通过smtp直接发送邮件 C# 带参访问接口,WebClient方式 C# 发送手机短信 文件 日志 写入 与读取
- C#中构建多线程应用程序[转] ----代码示例
- 【C#学习记录】通过“格式”菜单布局窗体
- c# .net 的async和await 异步编程(多线程编程) 在不同场景下的使用解析
- C#【多线程篇】Thread的IsBackground属性的使用
- 《C#多线程编程实战(原书第2版)》——1.4 线程等待
- 《C#多线程编程实战(原书第2版)》——1.5 终止线程
- 《C#多线程编程实战(原书第2版)》——1.9 向线程传递参数
- 《C#多线程编程实战(原书第2版)》——2.5 使用AutoResetEvent类
- QT-多线程重要概念及与界面之间交互总结(混淆点分析:c#中可以在子线程中创建ui控件,qt中不能在子线程中创建ui控件)
- C#多线程与UI响应 防止界面假死不响应(子线程创建的窗体获取消息响应用Application.DoEvent )
- C#中的多线程-线程同步基础 (控制线程数量)
- C#中利用委托实现多线程跨线程操作
- C#多线程之所有线程执行完成后
- 《敏捷软件开发:原则、模式与实践(C#版.修订版)》一2.3 参考文献
- C#多线程编程
- C# 多线程锁之ReaderWriterLockSlim
- C# 多线程学习系列三之CLR线程池系列之ThreadPool
- C# 多线程系列之异步回调(委托)
- C#-Monitor-多线程 不阻塞线程 无法执行时 放弃
- 5天玩转C#并行和多线程编程 —— 第三天 认识和使用Task
- C#多线程中的异常处理
- C#多线程中的异常处理
- C#模拟登录后请求查询
- C#多线程的实现
- 【C#/WPF】限制GridSplitter分隔栏的滑动范围