操作系统和计网
内存屏障
有两个东西会指令重排编译器(Compiler)和CPU 硬件,当然在单线程的时候,根据我们学过的流水线,这样可以提高吞吐量。
但是在多线程下可能导致乱序。
-
编译屏障(Compiler Barrier)
**只喊停编译器,不管 CPU。**如果确定CPU 本身不会乱序,那么它就够了。
-
CPU 内存屏障(Hardware Memory Barrier)
同时喊停编译器和 CPU 硬件。 不但让编译器不许重排,还插入一条特殊指令,强制 CPU 在执行到这里时,把之前乱序执行的尾巴收干净,后续指令必须严格等在后面。
四种乱序:
- 写-写重排:后一个写,跑到前一个写的前面去。
- 读-读重排:后一个读,跑到前一个读的前面去。
- 读-写重排:一个读,跑到后面一个写的后面;或者一个写跑到后面一个读的前面。
- 写-读重排:一个写之后的读,跑到写完成之前就执行了。
进而有下列屏障:
- 写屏障(Store Barrier / Write Barrier):只有写操作之间不能换顺序。
- 读屏障(Load Barrier / Read Barrier):只有读操作之间不能换顺序。
- 全屏障(Full Barrier):所有四种乱序都禁止
- Release 和 Acquire
- Release(发布):写操作。保证在它之前的所有读写,都完成之后,才轮到它执行
- Acquire(获取):读操作。保证在它之后的所有读写,必须等它完成之后才能开始。
用户态线程
用户态线程,就是完全在用户程序自己手里管理的线程,操作系统内核根本不知道它的存在。
优点:
- 极低开销:创建只需分配用户空间数据结构(通常 2~8 KB 栈),切换仅涉及少量寄存器的保存/恢复,无特权级切换。
- 超高并发:可在单个进程中容纳数十万甚至百万级并发单元,非常适合 I/O 密集型高并发场景(如网络服务器)。
- 灵活调度:应用可自定义调度策略,例如带优先级的协作调度、工作窃取(work-stealing)等,无需受限于内核通用调度器。
缺点:
- 阻塞隐患(N:1 模型下或 M:N 阻塞处理不当):任何直接的系统阻塞调用都可能阻塞整个调度域。
- 多核利用需显式设计:需要与应用层调度器配合,分配足够的内核线程并将就绪的 ULT 调度上去。
- 抢占困难:内核线程可依赖时钟中断强制抢占,用户态线程若缺乏语言/运行时支持(如 Go 的协作抢占基于栈扫描和抢占点),只能依赖显式让出(yield),容易因死循环导致其他线程饥饿。
- 资源隔离和公平性问题:用户态调度器通常对 CPU 时间无硬性保障,也难以与内核全局调度策略协调。
- 与现有同步原语的交互:用户态线程切换不提供任何内存屏障语义,对可见性的保证需由开发者通过并发原语显式声明。
模型:
一对一模型(1:1 Model): 每一个用户线程都映射到一个内核线程。说白了,就是给内核态套了个用户态的皮。
多对一模型(M:1 Model): 多个用户线程映射到一个内核线程。
-
一个线程阻塞 → 全部阻塞
-
系统调用会卡死整个进程
多对多模型(M:N Model):多个用户线程映射到多个内核线程(数量可不同)。
- 用户态负责“线程调度”,内核负责“线程执行”。
- 实现复杂