专用线程Thread、线程池ThreadPool、多线程、异步Async
线程Thread
Thread 是一个实际的操作系统级别的线程(OS 线程),有自己的栈和内核资源。Thread 允许最高程度的控制,你可以 Abort、Suspend 或 Resume 一个线程,你还可以监听它的状态,设置它的堆栈大小和 Culture 等属性。Thread 的开销成本很高,你的每一个线程都会为它的堆栈消耗相对较多的内存,并且在线程之间的处理器上下文切换时会增加额外的 CPU 开销。
默认为前台线程,主程序必须等线程跑完才会关闭,但是可以通过设置IsBackground,设置为后台线程。
线程池ThreadPool
ThreadPool(线程池)是一堆线程的包装器,由 CLR 维护。你对线程池中的线程没有任何控制权,你甚至无法知道线程池什么时候开始执行你提交的任务,你只能控制线程池的大小。简单来说,线程池调用线程的机制是,它首先调用已创建的空闲线程来执行你的任务,如果当前没有空闲线程,可能会创建新线程,也可能会等待(1s)。
可以使用 SetMinThreads 方法增加线程的最小数量。但是,在不必要的情况下增加这些值,可能会导致性能问题。如果同时启动的任务过多,则所有任务的处理速度看起来都可能很慢。大多数情况下,线程池使用自己的分配线程的算法将能够更好地工作。
确定线程池的大小
.NET应用中,一个进程采用线程池技术,默认线程个数为逻辑处理器的数量。
类型 | 线程池大小 | 备注 |
---|---|---|
CPU密集型 | CUP数量 | 计算操作 |
IO密集型 | IO任务数 | 磁盘、网络操作 |
实际应用时,保持net的默认设置即可满足实际的开发需要。
若追求极致和最大利用率,可采用下面的公式计算。
${最佳线程数量} = \frac{{等待时间+CPU时间}}{CPU时间}*CPU数量 $
即使有简单估算方法,但也需要结合系统真实情况(比如是IO密集型或者是CPU密集型或者是纯内存操作)和硬件环境(CPU、内存、硬盘读写速度、网络状况等),通过不断尝试达到一个符合实际的合理估算值。
线程与线程池
- 线程池中所有线程都是后台线程,如果进程的所有前台线程都结束了,所有的后台线程就会停止。不能把入池的线程改为前台线程。
- 不能给入池的线程设置优先级或名称。
- 对于COM对象,入池的所有线程都是多线程单元(Multi-threaded apartment,MTA)线程。许多COM对象都需要单线程单元(Single -threaded apartment,STA)线程。
- 入池的线程只能用于时间较短的任务。如果线程要一直运行(如Word的拼写检查器线程),就应使用Thread类创建一个线程。
只要有可能,就应该使用ThreadPool类来创建线程,即使用Task。不过在有些情况下,需要创建并管理自己的线程Thread,使用情景有:
需要具有特定优先级的任务。
有可能运行很长时间的任务(这样可能阻塞其他任务)。
需要确保只有一个线程可以访问特定的程序集。
需要有与线程相关的稳定标识。
异步与多线程
- 异步 : 属于通信的范畴,在发出消息当下不等待对方回应,便开始继续自己的任务。io 、socket
- 多线程 : 属于计算范畴,通常是关于如果利用 cpu 的空闲时间进行计算。
多线程是提高数据的计算能力,而异步则是为了提高程序的吞吐量。异步同多线程可以完美的结合,服务器开启多个线程监听前端请求,接收处理完毕后,可以异步地将数据序列化到磁盘,当然如果需要知道序列化的结果则异步处理可以等待 ( await )。