Thread 和 ThreadPool 简单梳理(C#)【并发编程系列】
〇、前言
对于 Thread 和 ThreadPool 已经是元老级别的类了。Thread 是 C# 语言对线程对象的封装,它从 .NET 1.0 版本就有了,然后 ThreadPool 是 .Net Framework 2.0 版本中出现的,都是相当成熟的存在。
当然,现在已经出现了 Task 和 PLinq 等更高效率的并发类,线程和线程池在实际开发中逐渐减少了,但是不能不知道他们的用法,因为总有需要对接的内容,别人用了你也得能看懂。
本文将结合示例,简单介绍下 Thread 和 ThreadPool。
一、Thread 类
Thread 类的功能就是,创建和控制线程,设置其优先级并获取其状态。
下边代码简单示例说明下 Thread 的相关内容:
public static void Main()
{
// (1)
//var th1 = new Thread(ExecuteInForeground);
//th1.Start();
// (2)
//var th2 = new Thread(ExecuteInForeground);
//th2.IsBackground = true;
//th2.Start();
// (3)
//ThreadPool.QueueUserWorkItem(ExecuteInForeground);
Thread.Sleep(1000);
// Console.WriteLine($"主线程 ({Thread.CurrentThread.ManagedThreadId}) 即将退出 执行 Join() 方法。。。");
// th2.Join();
Console.WriteLine($"主线程 ({Thread.CurrentThread.ManagedThreadId}) 即将退出。。。");
//Console.ReadLine();
}
private static void ExecuteInForeground(object state)
{
var sw = Stopwatch.StartNew();
Console.WriteLine("线程 {0}: {1}, 优先级: {2}",
Thread.CurrentThread.ManagedThreadId,
Thread.CurrentThread.ThreadState,
Thread.CurrentThread.Priority);
do
{
Console.WriteLine("线程 {0}: 计时 {1:N2} 秒",
Thread.CurrentThread.ManagedThreadId,
sw.ElapsedMilliseconds / 1000.0);
Thread.Sleep(500);
} while (sw.ElapsedMilliseconds <= 5000);
sw.Stop();
}
注释部分三组线程启动的结果如下三图:

第 1 部分,是前台线程,必须运行完毕,主线程才会退出,所以一直运行到 5s 之前。
第 2、3 部分,均为后台线程,当主线程运行完成之时,无论是否运行完成直接中断,所以只循环了两次就退出了。
关于 Join() 方法
代码中th2.Join()如果在后台线程上执行,这结果如下图,将会等待后台线程完成后主线程才结束。

二、ThreadPool 类
由于线程对象的创建时需要分配内存,GC 过程中销毁对象,然后整合零散的内存块,从而占用 CPU 资源,会影响程序性能,所以 ThreadPool 诞生了。
- 使用线程池,可以通过向应用程序提供由系统管理的工作线程池,来更有效的使用线程。
- 线程池可以通过重用线程、控制线程数量等操作,减少频繁创建和切换线程所带来的开销,从而提高响应速度。
- 可直接使用线程池中空闲的线程,而不必等待线程的创建,方便管理线程。
注意,托管线程池中的线程是后台线程,其 IsBackground 属性为 true。
1、ThreadPool 的几个属性值
- CompletedWorkItemCount:获取迄今为止已处理的工作项数。
- PendingWorkItemCount:获取当前已加入处理队列的工作项数。
- ThreadCount:获取当前存在的线程池线程数。
下面是一个关于线程池的几个属性值,以及开启新的后台线程并传入参数的实例:
//存放要计算的数值的字段
public static double num1 = -1;
public static double num2 = -1;
static void Main(string[] args)
{
int workerThreads, completionPortThreads;
// public static void GetMaxThreads (out int workerThreads, out int completionPortThreads);
ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
Console.WriteLine($"线程池中辅助线程的最大数目:{workerThreads}");
Console.WriteLine($"线程池中异步 I/O 线程的最大数目:{completionPortThreads}");
Console.WriteLine();
// public static void GetMinThreads(out int workerThreads, out int completionPortThreads);
ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);
Console.WriteLine($"线程池根据需要创建的最少数量的辅助线程:{workerThreads}");
Console.WriteLine($"线程池根据需要创建的最少数量的异步 I/O 线程:{completionPortThreads}");
Console.WriteLine();
ThreadPool.SetMaxThreads(100, 15); // set 的值必须是 Min~Max 之间的值,否则会设置不成功
ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
Console.WriteLine($"set 线程池中辅助线程的最大数目:{workerThreads}");
Console.WriteLine($"set 线程池中异步 I/O 线程的最大数目:{completionPortThreads}");
Console.WriteLine();
// 命名参数 传入后台线程
int num = 2;
// 启动第一个任务:计算x的8次方
Console.WriteLine("启动第一个任务:计算{0}的8次方.", num);
ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc1), num);
// 启动第二个任务
Console.WriteLine("启动第二个任务:计算{0}的8次方", num);
ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc2), num);
// 等待两个数值等完成计算
while (num1 == -1 || num2 == -1) ;
//打印计算结果
Console.WriteLine($"{num} 的 8 次方为 {num1} {num2}");
Console.ReadLine();
}
private static void TaskProc2(object state)
{
Console.WriteLine($"TaskProc2-Thread-{Thread.CurrentThread.IsBackground}");
num1 = Math.Pow(Convert.ToDouble(state), 8);
}
private static void TaskProc1(object state)
{
num2 = Math.Pow(Convert.ToDouble(state), 8);
}
输出结果:

2、由线程池生成一个可以取消的后台线程
如下代码,在没有单击回车键之前,程序会一直打印递增数字,当收到回车指令后,cts.Cancel();被执行,后台线程就取消成功了。
static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
ThreadPool.QueueUserWorkItem(t => Counts(cts.Token, 1000));
Console.WriteLine("Press Any Key to cancel the operation");
Console.ReadLine();
cts.Cancel();
Console.ReadLine();
}
private static void Counts(CancellationToken token, int CountTo)
{
for (int count = 0; count < CountTo; count++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Count is cancelled");
break;
}
Console.WriteLine(count);
Thread.Sleep(200);
}
Console.WriteLine("Count is stopped");
}
结果如下图:

三、Thread 和 ThreadPool 性能比较
如下代码,分别执行 100 次,看最终需要的时间成本:
public static void Main()
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 100; i++)
{
Thread th = new Thread(() =>
{
int count = 0;
count++;
});
th.Start();
}
sw.Stop();
Console.WriteLine("运行创建线程所需要的时间为:" + sw.ElapsedMilliseconds);
sw.Restart();
for (int i = 0; i < 100; i++)
{
ThreadPool.QueueUserWorkItem(t =>
{
int count = 0;
count++;
});
}
sw.Stop();
Console.WriteLine("运行线程池所需要花费的时间:" + sw.ElapsedMilliseconds);
Console.ReadLine();
}
如下图,明显线程池性能更佳:

参考:https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.threadpool?view=net-7.0
https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.thread?view=net-7.0
C#(ThreadPool)线程池的详解及使用范例 .NET(C#) ThreadPool线程池的使用总结
Thread 和 ThreadPool 简单梳理(C#)【并发编程系列】的更多相关文章
- [ 高并发]Java高并发编程系列第二篇--线程同步
高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...
- Java并发编程系列-(5) Java并发容器
5 并发容器 5.1 Hashtable.HashMap.TreeMap.HashSet.LinkedHashMap 在介绍并发容器之前,先分析下普通的容器,以及相应的实现,方便后续的对比. Hash ...
- Java并发编程系列-(4) 显式锁与AQS
4 显示锁和AQS 4.1 Lock接口 核心方法 Java在java.util.concurrent.locks包中提供了一系列的显示锁类,其中最基础的就是Lock接口,该接口提供了几个常见的锁相关 ...
- Java并发编程系列-(2) 线程的并发工具类
2.线程的并发工具类 2.1 Fork-Join JDK 7中引入了fork-join框架,专门来解决计算密集型的任务.可以将一个大任务,拆分成若干个小任务,如下图所示: Fork-Join框架利用了 ...
- Java并发编程系列-(1) 并发编程基础
1.并发编程基础 1.1 基本概念 CPU核心与线程数关系 Java中通过多线程的手段来实现并发,对于单处理器机器上来讲,宏观上的多线程并行执行是通过CPU的调度来实现的,微观上CPU在某个时刻只会运 ...
- Java并发编程系列-(6) Java线程池
6. 线程池 6.1 基本概念 在web开发中,服务器需要接受并处理请求,所以会为一个请求来分配一个线程来进行处理.如果每次请求都新创建一个线程的话实现起来非常简便,但是存在一个问题:如果并发的请求数 ...
- Java并发编程系列-(7) Java线程安全
7. 线程安全 7.1 线程安全的定义 如果多线程下使用这个类,不过多线程如何使用和调度这个类,这个类总是表示出正确的行为,这个类就是线程安全的. 类的线程安全表现为: 操作的原子性 内存的可见性 不 ...
- Java并发编程系列-(9) JDK 8/9/10中的并发
9.1 CompletableFuture CompletableFuture是JDK 8中引入的工具类,实现了Future接口,对以往的FutureTask的功能进行了增强. 手动设置完成状态 Co ...
- 干货:Java并发编程系列之volatile(二)
接上一篇<Java并发编程系列之synchronized(一)>,这是第二篇,说的是关于并发编程的volatile元素. Java语言规范第三版中对volatile的定义如下:Java编程 ...
- Java并发编程系列-(8) JMM和底层实现原理
8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线 ...
随机推荐
- Linux内存和磁盘管理(入门)
关于对Linux系统的内存和磁盘的操作 内存和磁盘使用率 查看内存使用率: free.top 查看磁盘使用率: fdisk.df.du free 以KB为单位: $ free total used f ...
- 逍遥自在学C语言 | 赋值运算符
前言 在C语言中,赋值运算符用于将一个值赋给变量 这个过程分为两个步骤: 计算赋值运算符右侧的表达式 将结果赋给左侧的变量. C语言提供了多个不同的赋值运算符,包括基本的赋值运算符.复合赋值运算符以及 ...
- MySQL-InnoDB磁盘结构
主要阐述InnoDB存储引擎(MySQL5以后的默认引擎). 数据库中最基本的组成结构是数据表,视觉上的表和其对应的磁盘结构如下: 此图参考了厦门大学课堂:MySQL原理 .但是视频中一些更多细节没有 ...
- Django笔记三十二之session登录验证操作
本文首发于公众号:Hunter后端 原文链接:Django笔记三十二之session登录验证操作 这一篇笔记将介绍 session 相关的内容,包括如何在系统中使用 session,以及利用 sess ...
- Prism Sample 20-NavigateToExistingViews
上一个例子介绍了INavigationAware中的OnNavitationTo,这次是第二个实现函数. IsNavitationTarget,这个名字有点误导,真实的作用是,当从其它页面导航至本页面 ...
- 2022-12-22:给定一个数字n,代表数组的长度, 给定一个数字m,代表数组每个位置都可以在1~m之间选择数字, 所有长度为n的数组中,最长递增子序列长度为3的数组,叫做达标数组。 返回达标数组的
2022-12-22:给定一个数字n,代表数组的长度, 给定一个数字m,代表数组每个位置都可以在1~m之间选择数字, 所有长度为n的数组中,最长递增子序列长度为3的数组,叫做达标数组. 返回达标数组的 ...
- 2022-10-07:给定员工的 schedule 列表,表示每个员工的工作时间。 每个员工都有一个非重叠的时间段 Intervals 列表,这些时间段已经排好序。 返回表示 所有 员工的 共同,正
2022-10-07:给定员工的 schedule 列表,表示每个员工的工作时间. 每个员工都有一个非重叠的时间段 Intervals 列表,这些时间段已经排好序. 返回表示 所有 员工的 共同,正数 ...
- Pyhton F字符串引起的invalid syntax
事发现场 偶然运行到之前写的爬虫,发现运行不了,报错invalid syntax,于是来找bug 报错截图: 原因: 这样用法称之为 f-string f-string,亦称为格式化字符串常量(for ...
- 【Python】爬虫下载视频
Python爬虫下载视频 前言 这两天我一时兴起想学习 PS ,于是去我的软件宝库中翻出陈年已久的 PhotoshopCS6 安装,结果发现很真流畅诶! 然后去搜索学习视频,网上的视频大多浮躁,收费, ...
- Blazor HyBrid在香橙派(Ubuntu Arm)运行的效果
Blazor HyBrid在香橙派(Ubuntu Arm)运行的效果 准备香橙派一块!当前教程使用的是香橙派5 4G开发板 准备.NET环境 安装.NET Core依赖 sudo apt instal ...