C# 异步编程基础(三)线程优先级、信号和线程池
此入门教程是记录下方参考资料视频的过程
开发工具:Visual Studio 2019
目录
C# 异步编程基础(六)Continuation 继续/延续 、TaskCompletionSource、实现 Task.Delay
C# 异步编程基础(十) 取消(cancellation)、进度报告、TAP(Task-Based Asynchronous Pattern)、Task组合器
线程优先级
- 线程的优先级(Thread的Proority属性)决定了相对于操作系统中其它活跃线程所占的执行时间
优先级高的线程比优先级低的线程所占的执行时间更多 - 优先级分为:
enum ThreadPriority
{
Lowest,
BelowNormal,
Normal,
AboveNormal,
Highest
}
提升线程优先级
- 提升线程优先级的时候需特别注意,因为它可能“饿死”其它线程
- 如果想让某线程(Thread)的优先级比其它进程(Process)中的线程(Thread)高,那就必须提升进程(Process)的优先级
使用System.Diagnostics下的Process类
using(Process p=Process.GetCurrentProcess())
{
p.PriorityClass=ProcessPriorityClass.High;
}
- 这可以很好地用于只做少量工作且需要较低延迟的非UI进程
- 对于需要大量计算的应用程序(尤其是有UI的应用程序),提高进程优先级可能会使其它进程饿死,从而降低整个计算机的速度
信号 Signaling
- 有时,你需要让某线程一直处于等待的状态,直至接收到其它线程发来的通知。这就叫做Signaling(发送信号)
- 最简单的信号结构就是ManualResetEvent
调用它上面的WaitOne方法会阻塞当前的线程,直到另一个线程通过调用Set方法来开启信号 - 例子,调用完Set之后,信号会于“打开”的状态,可以通过调用Reset方法将其再次关闭
static void Main(string[] args)
{
var signal=new ManualResetEvent(false);
new Thread(()=>
{
Console.WriteLine("Waiting for signal ...");
signal.WaitOne();
//主线程调用Set()时执行Dispose()
signal.Dispose();
Console.WriteLine("Get signal!");
}).Start();
Thread.Sleep(3000);
signal.Set();
}
线程池 Thread Pool
- 当开始一个线程的时候,将花费几百微秒来组织类似以下的内容:
一个新的局部变量栈(Stack) - 线程池就可以节省这种开销:
通过预先创建一个可循环使用线程的池来减少这一开销 - 线程池对于高效的并行编程和细粒度并发是必不可少的
- 它允许在不被线程启动的开销淹没的情况下运行短期操作
使用线程池,线程(池线程)需要注意的几点
- 不可以设置池线程的Name
- 池线程都是后台线程
- 阻塞池线程可能使性能降级
- 你可以自由的更改池线程的优先级
当它释放回池的时候,优先级将还原为正常状态 - 可以通过 Thread.CurrrentThread.IsThreadPoolThread 属性来判断是否执行在池线程上
进入线程池
- 最简单的、显式的在池线程运行代码的方式就是Task.Run
//Task is in System.Threading.Tasks
Task.Run(()=>Console.WriteLine("Hello from the thread pool"));
谁(哪些技术)使用了线程池
- WCF、Remoting、ASP.NET、ASMX Web Services应用服务器
- System.Timers.Timer、System.Threading.Timer
- 并行编程结构
- BackgroundWorker类(现在很多余)
- 异步委托(现在很多余)
线程池中的整洁
- 线程池提供了另外一个功能,即确保临时超出 Compute-Bound 的工作不会导致CPU超额订阅
- CPU超额订阅:活跃的线程超过CPU的核数,操作系统就需要对线程进行时间切片
- 超额订阅对性能影响很大,时间切片需要昂贵的上下文切换,并可能使CPU缓存失效,而CPU缓存对于现代处理器的性能至关重要
线程池中的整洁 CLR 的策略
- CLR通过对任务排队并对其启动进行节流限制来避免线程池中的超额订阅
- 它首先运行尽可能多的并发任务(只要还有CPU核),然后通过爬山算法调整并发级别,并在特定方向上不断调整工作负载
如果吞吐量提高,它将继续朝同一方向(否则将反转) - 这确保它始终追随最佳性能曲线,即使面对计算机上竞争的进程活动时也是如此
- 如果下面两点能够满足,那么CLR的策略将发挥出最佳效果:
工作项大多是短时间运行的(<250毫秒,或者理想情况下<100毫秒),因此CLR有很多机会进行测量和调整
大部分时间都被阻塞的工作项不会主宰线程池 - 如果想充分利用CPU,那么保持线程池的“整洁”是非常重要的