当前位置: 首页 > news >正文

编程深水区之并发⑥:C#的线程池

绝大多数情况下,我们都应该使用CLR线程池,而不是直接操作Thread,本章节介绍直接操作线程池的ThreadPool,但实际开发中也很少直接使用它。

一、CLR和线程池

1.1 CLR的主要工作

CLR(Common Language Runtime),公共语言运行时,是管理应用程序执行的运行时环境,类似Java的JVM虚拟机。之所以叫公共语言,是因为它支持多种语言编译为CLR执行的字节码,尽管绝大多数情况下都是使用C#。它的工作主要包括:

  • 托管应用程序代码
  • 加载和管理程序集
  • 内存分配和垃圾回收
  • 类型安全和代码验证
  • 异常处理
  • 调试诊断工具集
  • 代码执行,包括JIT编译(将中间字节码编译为本地机器码)和跨语言互操作
  • 安全管理,使用CAS策略和授权机制,限制代码可执行的操作权限
  • AppDomain, 比进程更轻量的隔离方式 ,有自己的安全策略、配置文件和垃圾回收,现在较少使用了
  • 线程管理,底层使用Windows操作系统进行管理和调度,但CLR提供了线程管理的API

1.2 应用程序和CLR实例是一对一关系吗?

正常情况下,每个应用程序都托管在一个CLR实例中,但由于.NET的技术发展历史,存在一些不一样的情况:

  • 早期的AspNet应用,托管在IIS中。IIS维护着一个应用线程池,只有一个CLR实例。CLR创建多个AppDomain,每个应用都在相互隔离的AppDomain中运行。所以,这些应用共享着一个CLR实例。但是,现在更加鼓励直接使用进程。
  • 现代的AspNetCore应用,无论是使用Kestrel独立运行,还是用IIS作为反向代理,或是在容器中运行,每个AspNetCore应用都拥有独立的进程和CLR实例,已不再依赖于AppDomain。
  • 桌面应用和控制台应用, 应用程序启动时,CLR会创建一个默认的AppDomain。应用程序的所有代码和资源都会加载到这个默认的AppDomain中,一个应用对应一个CLR实例。

1.3 CLR和线程池

创建和销毁线程的开销很大,太多的线程不但浪费资源,也会影响GC性能,所在CLR管理着一个线程池。如果CLR实例中有AppDomain,则所有AppDomain共享这个线程池。如果一个进程加载了多个CLR,则每个CLR都有一个线程池。
CLR初始化时,线程池中还没有线程。线程池维护着一个操作请求队列,当应用程序执行一个异步操作时,会将这个异步任务追加到队列中。线程池从队列中取出任务,并派发给一个线程池中的线程。如果线程池中没有线程,会自动创建一个新线程,这和创建线程的开销没有什么大的区别。重要在于,这个线程完成任务后,并不会销毁,而是返回线程池,进入空闲状态,等待响应另外一个请求。如果异步请求非常多,线程池会自动扩充线程数量,以适应繁忙的请求;当请求减少时,就会有比较多的线程空闲下来并进入休眠状态,休眠时间超过一个阀值,线程会自己醒来,并干掉自己,以释放资源。
正常情况下,CLR会根据CPU的核数,快速创建初始数量的线程,以充分利用多核CPU的性能,这通常也是线程池的最小线程数。System.Threading.ThreadPool类提供了几个静态方法,可以获取和设置线程池的最小和最大数量:GetMaxThreads、SetMaxThreads、GetMinThreads、SetMinThreads。但强列建议不要去设置,处理这些问题,CLR比我们更聪明。

1.4 线程池的调度原理

CLR线程池的调度工作原理,如下所示:

  • 主线程将线程池异步任务(使用ThreadPool、Task、Timer等创建的异步任务),放入到CLR线程池的全局队列中。工作者线程(以下称之为异步线程)按先入先出的规则,从全局队列中取出异步任务,这时可能出现多个异步线程同时取同一个任务的情况,为以保障多个线程不会取到同一个任务,所有线程都要先竞争这个任务的线程同步锁。锁定后,其它线程就不会再竞争这个任务。
  • 当异步线程取出任务后,会放入属于自己的本地队列中,再按后入先出的规则取出任务进行处理。由于本地队列只属于自己,所以此时不需要同步锁。
  • 如果异步线程发现它的本地队列空了,会尝试从另一个异步线程的本地队列的尾部中“偷”一个任务,此时会请求获取这个任务的线程同步锁。
  • 当所有本地队列都空了,就会尝试从全局队列中取任务,如果也空了,就会进入睡眠状态。如果睡眠太长时间,会自己醒来,并销毁自身,GC会在适当时候回收线程使用的资源。


注意:CLR线程池,不仅只有工作者线程,还有I/O线程,将在I/O异步操作章节详述

二、使用ThreadPool

2.1 使用ThreadPool创建(线程池)线程

//以下分别使用传入方法和传入Lambda的方式,创建线程
//使用QueueUserWorkItem()方法创建,结合上节原理,"Queue"很贴切
public class Program
{static void Main(){var ct = Thread.CurrentThread;Console.WriteLine($"{ct.ManagedThreadId}:主线程开始");//方式1:传入方法ThreadPool.QueueUserWorkItem(NoParamWorker);//方式2:传入Lambda//ThreadPool.QueueUserWorkItem((state)=>{...},实参),state为形参ThreadPool.QueueUserWorkItem((state) => {var ct = Thread.CurrentThread;Console.WriteLine($"{ct.ManagedThreadId}:Worker拿到参数{state}");},5);}static void NoParamWorker(object state){var ct = Thread.CurrentThread;Console.WriteLine($"{ct.ManagedThreadId}:Worker执行不传参的任务");}
}
/*输出
1:主线程开始
6:Worker线程执行不传参的任务
7:Worker线程拿到参数5
*/

2.2 使用CancellationTokenSource终止线程

上节我们使用共享变量来终止线程,这节使用终止线程的专用对象CancellationTokenSource,两者原理和用法相似。直白的说,就是向线程传入一个信号令牌(Token),可以在线程外部发送终止信号(Cancel),线程收到终止信号后(Token.IsCancellationRequested),由自己来终止线程。外部直接终止线程是危险的操作,之前可以用的Abort(),现在已经不能使用。只能传入信号,由线程自己终止自己,这样可以安全的释放内存。

//1、在异步线程中循环输出数字,然后在主线程中终止异步线程==========
public class Program
{static void Main(){//创建Token信号令牌源,一个源可以关联多个令牌var cts = new CancellationTokenSource();//创建线程,并传入Token信号令牌ThreadPool.QueueUserWorkItem((state) => CountWorker(cts.Token, 10000) );Console.WriteLine("输入Enter,终止异步操作");Console.ReadLine();//发送终止信息cts.Cancel(); Console.ReadLine();}static void CountWorker(CancellationToken token, int num){var ct = Thread.CurrentThread;Console.WriteLine($"{ct.ManagedThreadId}:Worker线程开始执行任务");for (int i = 0; i < num; i++){if (!token.IsCancellationRequested) //判断是否收到外部的终止信号{Console.WriteLine(i);Thread.Sleep(200);//模拟耗时等待}else {Console.WriteLine("任务被终止了");break;//退出循环}}}
}//2、CancellationTokenSource的其它API================================
//2.1 注册异步线程终止后的回调,可以注册多个
var cts = new CancellationTokenSource();
cts.Token.Register(() => { Console.WriteLine("终止回调1"); });
cts.Token.Register(() => { Console.WriteLine("终止回调2"); });//2.2 调用上例的CountWorker,不更改方法签名的情况下,屏蔽外部的终止信息
//此时外部调用【cts.Cancel();】,将无法终止线程
ThreadPool.QueueUserWorkItem((state)=>CountWorker(cts.Token.None, 10000));//2.3 关联多个cts,略
//CancellationTokenSource.CreateLindedTokenSource(cts1.Token,cts2.Token)

2.3 多线程的执行上下文

执行上下文,文字上理解是一个比较抽象的概念。但是,在数据层面,它本质是一个包含着多层嵌套的复杂对象,记录着当前环境的一些信息, 在程序流转时,各个环节都可以读取或设置这个对象。如果做.NET后端,最熟悉的上下文,应该就是HttpContext。
执行上下文,一般包括安全设置、宿主信息、上下文数据等,具体内容根据不同运行环境会有差异。当CLR初始化时,会为主线程创建执行上下文,默认情况下,当一个线程(主线程)使用另外一个线程(辅线程)时,前者的执行上下文会流向(复制)辅线程。如果在辅助线程里再开辅助线程,也是遵循这样的规律。
绝大多数情况下,按默认方式传递是最优的,但这种流向还是会消耗一些性能。如果想极致提升性能,而辅助线程又用不到执行上下文,可以手动阻止上下文流动。
大概知道咋回事就行了,例子就不举了。

三、后记

我们很少直接使用ThreadPool,因为它有一些限制,比如你无法知道异步任务什么时候完成,也无法让异步操作返回值。而System.Threading.Tasks命名空间下的类型,能够解决这些技术问题,它建立在ThreadPool基础之上,仍然是使用CLR线程池。


*这是一个系列文章,将全面介绍多线程、用户态协程和单线程事件循环机制,建议收藏、点赞哦!
*你在并发编程过程中碰到了哪些难题?欢迎评论区交流~~~


我是functionMC > function MyClass(){…}
C#/TS/鸿蒙/AI等技术问题,以及如何写Bug、防脱发、送外卖等高深问题,都可以私信提问哦!

C#线程池.png

相关文章:

编程深水区之并发⑥:C#的线程池

绝大多数情况下&#xff0c;我们都应该使用CLR线程池&#xff0c;而不是直接操作Thread&#xff0c;本章节介绍直接操作线程池的ThreadPool&#xff0c;但实际开发中也很少直接使用它。 一、CLR和线程池 1.1 CLR的主要工作 CLR&#xff08;Common Language Runtime&#xff0…...

KCTF 闯关游戏:1 ~ 7 关

前言 看雪CTF平台是一个专注于网络安全技术竞赛的在线平台&#xff0c;它提供了一个供网络安全爱好者和技术专家进行技术交流、学习和竞技的环境。CTF&#xff08;Capture The Flag&#xff0c;夺旗赛&#xff09;是网络安全领域内的一种流行竞赛形式&#xff0c;起源于1996年…...

【海贼王航海日志:前端技术探索】一篇文章带你走进JavaScript(二)

目录 1 -> 基础数据类型 1.1 -> 条件语句 1.1.1 if语句 1.2 -> 分支语句 1.2.1 -> switch语句 1.3 -> 循环语句 1.3.1 -> while循环 1.3.2 -> continue 1.3.3 -> break 1.3.4 -> for循环 1.4 -> 数组 1.4.1 -> 创建数组 1.4.2 -…...

鸿蒙内核源码分析(进程管理篇) | 谁在管理内核资源?

官方基本概念 从系统的角度看&#xff0c;进程是资源管理单元。进程可以使用或等待CPU、使用内存空间等系统资源&#xff0c;并独立于其它进程运行。 OpenHarmony内核的进程模块可以给用户提供多个进程&#xff0c;实现了进程之间的切换和通信&#xff0c;帮助用户管理业务程序…...

SQLALchemy 自动从数据库中映射

SQLALchemy 自动从数据库中映射 使用`automap_base`注意事项在SQLAlchemy中,自动从数据库中映射表到Python类(也称为“反射”或“逆向工程”)是一个常见的需求,尤其是在你已经有了一个现有的数据库,并希望快速地为它创建一个ORM模型时。SQLAlchemy提供了工具来帮助你完成这…...

C++ stack与queue的使用与简单实现

目录 0. 适配器 1. stack的简要介绍 2. stack的简单使用 3. queue的简要介绍 4. queue的简单使用 STL标准库中stack和queue的底层结构 deque简单介绍 5. stack的模拟实现 6. queue的模拟实现 0. 适配器 在文章开始前我们先了解一下适配器的概念 适配器是一种设计模式(设计…...

【CS.DB】数据库-关系型数据库-MySQL-3.3.创建和管理表

1000.04.CS.DB-Database-Relational-MySQL-3.3.创建和管理表-Created: 2023-03-08.Thursday17:39 1. 创建和管理表 在 MySQL 中&#xff0c;创建和管理表是数据库操作的基础。以下是创建和管理表的主要步骤和方法。 1.1 定义表结构 定义表结构包括指定表的名称、列的名称和数…...

Ceph分布式存储系统的搭建与使用

目录 一. 环境准备 二. 安装Docker 三. admin节点安装cephadm 四. admin节点给另外四个主机导入镜像 五. 向集群中添加节点 六. Ceph使用 列出可用设备 清除设备数据---针对有数据的设备 检查 OSD 状态 Ceph 集群中添加一个新的 OSD 查看集群的健康状态 指定MDS 列…...

通过Redsocks将Kali Linux的流量进行代理

Redsocks 是一个代理重定向工具&#xff0c;可以将流量通过 SOCKS 或 HTTP 代理传递。你可以使用它在 Kali Linux 中将流量通过代理服务器。以下是设置和使用 Redsocks 的步骤&#xff1a; 1. 安装 Redsocks Redsocks 通常在 Kali Linux 上不可用&#xff0c;需要手动安装。首…...

基于java五台山景点购票系统(源码+论文+部署讲解等)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台的优…...

基于springboot的网上服装商城

TOC springboot182基于springboot的网上服装商城 第一章 课题背景及研究内容 1.1 课题背景 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性…...

QT、C++简单界面设计

#include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {---------------------窗口设置----------------------this->setWindowTitle("南城贤子摄影工作室");//设置窗口标题this->setWindowIcon(QIcon("d:\\Pictures\\C…...

代码随想录算法训练营43期 | Day 10——栈与队列part1

代码随想录算法训练营 代码随想录算法训练营43期 | Day 10232.用栈实现队列225. 用队列实现栈20. 有效的括号1047.删除字符串中的所有相邻重复项 代码随想录算法训练营43期 | Day 10 232.用栈实现队列 class MyQueue { public:stack<int> sIn;stack<int> sOut;My…...

Java中常用的设计模式

一、什么是设计模式 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程…...

leetcode 11-20(2024.08.15)

立个flag&#xff0c;1-100题每天分配10题&#xff0c;不会就先空着&#xff08;7&#xff09;。 1. 11&#xff1a;盛最多水的容器 class Solution:def maxArea(self, height: List[int]) -> int:res 0left 0right len(height) - 1while left < right:area (right…...

C语言整数溢出的问题

目录 补漏&#xff1a; 问题展现 解析 C的解决方案 补漏&#xff1a; 昨天我在开头提到-1的二进制如何表示&#xff0c;我在这里简单分析一下。 首先我们要明白有符号的数转换是需要补码的&#xff0c;所以我们想这个问题之前将补码的规则思考一遍&#xff08;首先将有符号…...

Linux学习之路 -- 进程 -- 进程间通信 -- 管道通信

本文主要介绍进程通信中的管道通信。 前面我们学习进程的过程中&#xff0c;我们知道&#xff0c;进程是具有独立性的。这也就导致了进程不能够直接地把数据进行传递。为了实现进程之间地通信&#xff0c;我们就需要通过另外地方式来实现进程之间数据地传递。 1.进程通信的目…...

GB/T 38082-2019 生物降解塑料购物袋检测

生物降解塑料购物袋是指以生物降解树脂为主要原料制得的&#xff0c;具有提携结构的&#xff0c;在销售、服务等场所用于盛装及携提商品的袋制品。 GB/T 38082-2019 生物降解塑料购物袋检测项目&#xff1a; 检测项目 测试标准 尺寸偏差 GB/T 38082 感官 GB/T 38082 提掉…...

docker数据卷和资源控制

目录 数据卷 实现数据卷 宿主机和容器之间进行数据共享 容器与容器之间进行数据共享 容器互联 docker容器的资源控制 cpu 1.设置cpu资源控制&#xff08;比重&#xff09; 2. 设置cpu的资源占用比&#xff08;权重&#xff09; 3.设置容器绑定cpu 内存 1.内存限制 …...

Kafka系统及其角色

Apache Kafka系统介绍 Apache Kafka 是由 LinkedIn 公司最初开发的一个高性能、分布式的消息传递系统。它被设计为一个可扩展、持久、分布式的流式处理平台&#xff0c;以满足 LinkedIn 在实时数据处理方面的需求 。Kafka 的诞生源于 LinkedIn 需要处理海量数据时现有消息队列系…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强&#xff0c;React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 &#xff08;1&#xff09;使用React Native…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

Matlab | matlab常用命令总结

常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

【Go语言基础【13】】函数、闭包、方法

文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数&#xff08;函数作为参数、返回值&#xff09; 三、匿名函数与闭包1. 匿名函数&#xff08;Lambda函…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能

1. 开发环境准备 ​​安装DevEco Studio 3.1​​&#xff1a; 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK ​​项目配置​​&#xff1a; // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...

怎么让Comfyui导出的图像不包含工作流信息,

为了数据安全&#xff0c;让Comfyui导出的图像不包含工作流信息&#xff0c;导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo&#xff08;推荐&#xff09;​​ 在 save_images 方法中&#xff0c;​​删除或注释掉所有与 metadata …...