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

C#线程底层原理知识

前言:底层原理与上层知识对应关系

上位机要用的技术 (清单内容)对应的深度知识 (为什么学这个)实际解决的上位机痛点
async / await异步状态机 / 上下文切换保证你一边读取 PLC 数据,一边操作 UI 界面,界面绝不假死
Interlocked (原子操作)内存模型 / 指令重排在高速流水线计数时,保证数据一个都不丢,不需要沉重的lock
ConcurrentCollectionsCAS 无锁编程 / 工作窃取多个相机、传感器同时往缓存写数据时,程序不会崩溃,性能最高。
SemaphoreSlim内核模式 vs 用户模式锁控制有限的硬件连接数(如 PLC 只有 4 个连接口),实现资源排队
CancellationToken线程协作机制当按下“紧急停止”按钮时,能优雅且瞬间关闭所有后台扫描线程。

一内存模型与可见性

1.硬件知识

  • 主内存(内存条)所有全局变量、静态变量,真实原始数据都存在这里,速度最慢。

  • CPU 高速缓存(L1/L2/L3 缓存)每个 CPU 核心自带小仓库,速度极快,比内存条快几十上百倍。线程跑在 CPU 核心上,优先读写自己的缓存,不爱读慢速的内存条。

  • 线程一个 CPU 核心同一时间跑一个线程;多线程 = 多个核心同时干活,或一个核心快速切换。

2.什么是 计算机内存模型

1.定义

内存模型 = 一套规则

规定

CPU、缓存、主内存之间,数据怎么读、怎么写、指令怎么排序、数据什么时候同步

2.为什么需要

CPU 为了跑得快,会私自做两件事优化性能:

  1. 缓存隔离:变量先存在自己缓存,不实时同步到内存
  2. 指令重排:打乱代码执行顺序,乱序执行提升效率

这套乱搞的规则 + 数据同步规则,合起来就是内存模型

3.数据同步规则

系统默认是没有同步规则的

规则 / 机制对应工具解决的核心问题
1. 原子性Interlocked保证count++这类操作不可拆分,要么全成,要么全不成
2. 互斥访问lock/Monitor/SemaphoreSlim同一时间只允许一个线程进入临界区,避免同时修改
3. 可见性volatile/ 内存屏障保证一个线程对变量的修改,其他线程能立刻看到
4. 禁止指令重排volatile/lock自带内存屏障防止编译器 / CPU 为了优化打乱执行顺序,导致半初始化对象被读取

4.什么是 可见性

场景举例

有一个全局变量:

static bool flag = false;
  • 线程 A:循环判断flag,只要是 false 就一直跑
  • 线程 B:隔一段时间,把flag = true

理想情况(正常人理解)

B 改了 flag → 内存立刻更新 → A 马上读到新值 → 循环结束。

现实 CPU 情况(出问题的根源)

  1. 线程 A 启动,把flag主内存读到自己 CPU 缓存
  2. 之后 A 一直只读自己缓存里的旧值:false
  3. 线程 B 修改flag = true,只改了B 自己的缓存
  4. B没有立刻把数据写回主内存
  5. 主内存还是旧值,A 的缓存也永远是旧值

👉 最终结果:B 明明修改了变量,A 完全看不见,程序死循环卡死

✅ 这就叫:可见性问题

可见性:一个线程对共享变量的修改,能不能被其他线程及时看到

5.为什么会出现【不可见】

1.每个CPU有独立缓存,互相隔离

2.线程默认优先读写本地缓存,不频繁读写慢速主内存

3.写操作不会立刻刷新到主内存,缓存和内存数据不一样

3.C#里怎么解决可见性

1.volatile关键字(专门解决可见性)

作用:

  • 读:强制每次都从主内存读最新数据,不读缓存
  • 写:强制修改立刻刷回主内存
  • 禁止 CPU 随便打乱代码顺序

加上之后:B 改完变量立刻同步到内存,A 下一次读取直接拉内存最新值,互相可见

注意:volatile 只管「看得见」,不管i++这种多步操作的安全(原子性)。

2.lock/ Monitor

加锁会自动:

  • 进入锁:刷新缓存,读取最新内存数据
  • 退出锁:把修改强制写回主内存所以 lock自带解决可见性,功能更强。

二指令重排

CPU 为了提速,会打乱代码执行顺序

单线程没事,多线程下会出现逻辑错乱volatile同时会禁止指令重排,保证代码执行顺序不乱。

三线程同步语

三个层次 ,由低到高

原子操作 → 内存屏障 (volatile) → 互斥锁 (lock)

层级越低,越好理解、越贴近硬件;层级越高、功能越强、越慢

第一层:原子操作(硬件层级:最轻量)

1. 是什么

CPU 硬件直接支持的不可拆分操作。一句话:要么做完,要么没做,不会做一半被抢走。

2. 能干啥

只解决一个问题:原子性比如i++本来是三步:1. 读值 → 2. 计算 → 3. 写回容易被其他线程插队改错。

用原子操作(C#Interlocked):

Interlocked.Increment(ref count);

硬件保证:这一整步绝不被打断

3. 特点

  • 纯硬件指令,速度最快
  • 能力最弱:✅ 保证原子性❌ 不保证可见性❌ 不保证代码执行顺序

第二层:内存屏障 /volatile(软件 + 硬件层级)

1. 是什么

给 CPU 下「强制命令」:

  1. 不许乱序执行代码(禁止指令重排)
  2. 不许私自缓存变量,强制读写主内存

2. 能干啥

解决两个问题:可见性 + 有序性

  • 可见性:A 线程改完变量,B 线程立刻能看到最新值
  • 有序性:代码按你写的顺序跑,CPU 不能偷偷调换

3. 对应 C#

volatile关键字、内存屏障MemoryBarrier

4. 特点

  • 无阻塞、无等待、速度中等
  • 短板明显:✅ 保证可见性、有序性❌不保证原子性依然挡不住count++并发错乱

第三层:互斥锁(操作系统层级・最重)

1. 是什么

最通俗:抢厕所模式一把锁同一时间只允许一个线程进去执行代码,其他人排队等着。

C# 对应:lockMonitor、Mutex

2. 能干啥

全包解决三大问题

  1. 原子性:临界区代码整块执行,不被插队
  2. 可见性:加锁 / 解锁自动刷新内存缓存
  3. 有序性:锁范围内指令禁止乱重排

3. 特点

  • 功能最强、最安全
  • 代价最大:会阻塞线程、上下文切换、速度最慢

总结

层级代表技术解决问题性能
1 原子操作Interlocked原子性最快
2 内存屏障volatile可见性、有序性中等
3 互斥锁lock / Monitor原子 + 可见 + 有序最慢

四异步状态机

1.什么是机

这里的 “机”,不是 “机器” 那个冷冰冰的意思,而是 **“机制 / 装置 / 系统”** 的简称,英文是Machine,指的是一套能自动运转、按规则干活的东西。就像:

  • 蒸汽机:靠蒸汽推动的装置
  • 发动机:靠燃料推动的装置
  • 状态机:靠 “状态” 来驱动运转的装置

合起来,状态机(State Machine)就是:

一套以状态为核心,根据触发条件在不同状态之间切换,并在每个状态下执行固定行为的自动化机制。

异步状态机,就是把一段可暂停、可恢复的代码流程,拆成多个 “状态”,靠状态切换来管理异步执行(不卡线程、等待 IO / 耗时操作)。C# 里async/await的底层本质就是编译器自动生成的异步状态机

2.基本概念

1. 状态机(State Machine)

  • 一个对象 / 流程,只有有限个状态(如:未开始、运行中、等待中、完成、出错)
  • 触发条件(事件、await、信号)从一个状态跳到另一个
  • 每个状态下有固定行为

生活例子:微波炉

  • 状态:关门待机 → 启动 → 加热中 → 暂停 → 加热完成 → 开门
  • 触发:按 “开始”、定时到、开门、按暂停

2. 异步(Async)

  • 不阻塞当前线程:等网络、文件、数据库时,线程去干别的,不傻等
  • 完成后再回来继续执行

3. 异步状态机

  • 把异步方法切成多个 “片段”(每个await切一刀)
  • 用状态记录 “执行到哪了
  • 支持:暂停 → 保存现场 → 等待 → 恢复现场 → 继续跑
  1. = 一个自动干活的小工具 / 小机器
  2. 状态= 记录你代码跑到哪一步
    • 状态 - 1:还没开始
    • 状态 0:刚跑完 await 前面
    • 状态 - 2:全部跑完结束
  3. 异步= 等待的时候不卡界面、不卡线程

合起来:异步状态机 = 专门帮 async 方法记住进度、暂停、后续接着跑的后台小工具

五工作窃取

  • 传统线程池:大家共用一个任务队列。
    • 一个线程干完活,去公共队列取 →大家抢同一个队列,锁竞争大
    • 有的线程忙死,有的闲死 →CPU 利用率低
  • 工作窃取线程池(如 JavaForkJoinPool、.NETTaskScheduler底层):
    1. 每个线程有自己的私有任务队列(双端队列 Deque)。
    2. 线程优先从自己队列的 “尾部” 拿任务(LIFO,自己的活自己先干)。
    3. 自己队列为空 → 去偷别人队列 “头部” 的任务(FIFO)。
    4. 头尾分离:自己从尾拿、别人从头偷 →几乎不冲突、不用锁(CAS)

总结:工作窃取 = 线程自带私队 + 自己干完偷别人 + 头尾分离少竞争 → 负载均衡、CPU 拉满

六上下文切换

1.定义

上下文切换:CPU 现在正在跑「线程 A」,临时暂停、保存 A 的所有数据,切去跑「线程 B」,等下再切回来继续跑 A,这个来回切的过程,就叫上下文切换


2. 什么是「上下文」?

就是这个线程当下的全部现场数据

  • 寄存器值
  • 程序执行到哪一行
  • 栈数据、状态标记

好比:你写作业写到一半,把笔放下、本子合上、记好写到第几题去洗碗,洗完再翻开本子、接着刚才位置写。👉 合上 + 记录 + 切换干活 = 上下文切换


3. 为什么会发生上下文切换?

三种最常见情况:

  1. 时间片用完CPU 公平起见,每个线程只给一小段时间,时间到强制切走。
  2. 线程阻塞 / 等待比如Thread.Sleep、等待 IO、锁等待、await挂起,线程没事干,CPU 切去跑别的线程。
  3. 主动让步代码主动Thread.Yield让出 CPU。

4. 关键重点(必记)

① 上下文切换 = 有开销(费性能)

保存现场 + 恢复现场 + 切换调度,要耗 CPU、耗时间

  • 切换越少 → 程序越快
  • 疯狂频繁切换 → 性能暴跌、卡顿

② 两种切换(简单区分)

  1. 用户态切换:只切线程,开销小
  2. 内核态切换:进系统内核操作,开销很大(尽量避免)

七真假异步

http://www.jsqmd.com/news/689804/

相关文章:

  • 2026年4月武汉沸石滤料直销工厂专业评估:为何坚凝工程材料有限公司值得关注? - 2026年企业推荐榜
  • 【CSS魔法实战】打造吸睛网页的4种文字视觉特效
  • 手把手教你用MuJoCo XML构建一个闭链机器人模型(附完整代码)
  • 跨端语音直播实战:基于UniApp与WebRTC构建多平台(App+H5)互动房间的架构与核心实现
  • 2026年4月新消息:荆门健康风干鱼源头厂家的品质坚守与创新之路 - 2026年企业推荐榜
  • 新概念英语第二册29_Taxi
  • 亦庄人形机器人半程马拉松:大厂入局改写竞争规则,赛事成具身智能行业新秩序催化剂
  • 【无人机三维路径规划】基于遗传算法GA实现无人机三维路径规划附Matlab代码
  • ROS2节点、话题、服务傻傻分不清?一张图+三个生活比喻帮你彻底理清
  • 深度学习入门:tf.keras核心组件与实战指南
  • 别再用虚拟机了!在Windows 11的WSL2里用CentOS 8配置Spark伪分布式环境
  • 2026年4月大平层装修全案设计领航者:江西序文空间设计装饰工程有限公司深度解析 - 2026年企业推荐榜
  • CTF实战:用Python脚本爆破CRC32找回压缩包里的隐藏密码(附完整代码)
  • DXF解析成运动控制指令DEMO源代码:支持缩放与多图层控制
  • 从零拆解STM32F103 IAP Bootloader:代码结构与跳转机制深度剖析
  • 超越默认值:OpenCV SGBM在无人机避障与机器人导航中的参数优化实战
  • 为什么晒红的茶汤是“红亮”而不是“红浓”?
  • 纳米级时间分辨电子显微镜热测量技术解析
  • TI毫米波雷达AWR1642+DCA1000EVM新手避坑全记录:从电源选型到FPGA配置的保姆级教程
  • 不只是改串口:全志A133 Android 10 GPS HAL层(gnsshal)深度配置与天线选型指南
  • 避开这些坑,你的SCI图片投稿一次过!关于位图、矢量图和.tiff/.eps格式的终极指南
  • 2020年MLOps技术演进与实战经验总结
  • 详细解析的电动汽车充电站能量调度策略程序
  • 微信聊天记录永久保存终极指南:如何完整备份与分析你的数字记忆
  • 从特征提取到微调:为什么你的RoBERTa在MELD情感分类上效果差?我的调参踩坑实录
  • Imaris新手避坑指南:从TIF序列到3D模型的保姆级导入流程(含FIJI格式转换)
  • 5步精通:开源跨平台B站视频下载终极指南
  • 【教学类-160-09】20260417 AI视频培训-练习010“豆包AI视频《熊猫找朋友》+豆包图片风格:水墨画”
  • SAP生产订单报工避坑指南:BAPI_PRODORDCONF_CREATE_TT调用时,如何处理可报工数量与工时计算?
  • 基于GSConv-BiLSTM的多变量时间序列预测模型附Matlab代码