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 为了跑得快会私自做两件事优化性能缓存隔离变量先存在自己缓存不实时同步到内存指令重排打乱代码执行顺序乱序执行提升效率这套乱搞的规则 数据同步规则合起来就是内存模型。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 情况出问题的根源线程 A 启动把flag从主内存读到自己 CPU 缓存之后 A 一直只读自己缓存里的旧值false线程 B 修改flag true只改了B 自己的缓存B没有立刻把数据写回主内存主内存还是旧值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#InterlockedInterlocked.Increment(ref count);硬件保证这一整步绝不被打断。3. 特点纯硬件指令速度最快能力最弱✅ 保证原子性❌ 不保证可见性❌ 不保证代码执行顺序第二层内存屏障 /volatile软件 硬件层级1. 是什么给 CPU 下「强制命令」不许乱序执行代码禁止指令重排不许私自缓存变量强制读写主内存2. 能干啥解决两个问题可见性 有序性可见性A 线程改完变量B 线程立刻能看到最新值有序性代码按你写的顺序跑CPU 不能偷偷调换3. 对应 C#volatile关键字、内存屏障MemoryBarrier4. 特点无阻塞、无等待、速度中等短板明显✅ 保证可见性、有序性❌不保证原子性依然挡不住count并发错乱第三层互斥锁操作系统层级・最重1. 是什么最通俗抢厕所模式一把锁同一时间只允许一个线程进去执行代码其他人排队等着。C# 对应lock、Monitor、Mutex2. 能干啥全包解决三大问题原子性临界区代码整块执行不被插队可见性加锁 / 解锁自动刷新内存缓存有序性锁范围内指令禁止乱重排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还没开始状态 0刚跑完 await 前面状态 - 2全部跑完结束异步 等待的时候不卡界面、不卡线程合起来异步状态机 专门帮 async 方法记住进度、暂停、后续接着跑的后台小工具五工作窃取传统线程池大家共用一个任务队列。一个线程干完活去公共队列取 →大家抢同一个队列锁竞争大。有的线程忙死有的闲死 →CPU 利用率低。工作窃取线程池如 JavaForkJoinPool、.NETTaskScheduler底层每个线程有自己的私有任务队列双端队列 Deque。线程优先从自己队列的 “尾部” 拿任务LIFO自己的活自己先干。自己队列为空 → 去偷别人队列 “头部” 的任务FIFO。头尾分离自己从尾拿、别人从头偷 →几乎不冲突、不用锁CAS总结工作窃取 线程自带私队 自己干完偷别人 头尾分离少竞争 → 负载均衡、CPU 拉满。六上下文切换1.定义上下文切换CPU 现在正在跑「线程 A」临时暂停、保存 A 的所有数据切去跑「线程 B」等下再切回来继续跑 A这个来回切的过程就叫上下文切换。2. 什么是「上下文」就是这个线程当下的全部现场数据寄存器值程序执行到哪一行栈数据、状态标记好比你写作业写到一半把笔放下、本子合上、记好写到第几题去洗碗洗完再翻开本子、接着刚才位置写。 合上 记录 切换干活 上下文切换3. 为什么会发生上下文切换三种最常见情况时间片用完CPU 公平起见每个线程只给一小段时间时间到强制切走。线程阻塞 / 等待比如Thread.Sleep、等待 IO、锁等待、await挂起线程没事干CPU 切去跑别的线程。主动让步代码主动Thread.Yield让出 CPU。4. 关键重点必记① 上下文切换 有开销费性能保存现场 恢复现场 切换调度要耗 CPU、耗时间。切换越少 → 程序越快疯狂频繁切换 → 性能暴跌、卡顿② 两种切换简单区分用户态切换只切线程开销小内核态切换进系统内核操作开销很大尽量避免七真假异步