信号抢占 vs 信号量,理清两套调度底层机制

发布时间:2026/8/11 20:15:30
信号抢占 vs 信号量,理清两套调度底层机制 前言学习 GMP 调度模型时极易混淆两组概念操作系统信号Signal、Go 运行时信号量Semaphore。 不少人会简单认为二者都是 “通知协程”本质完全不同信号Signal操作系统内核提供的中断机制用于实现Go1.14 异步抢占调度信号量Semaphore运行态实现的休眠 / 唤醒原语用于协程、线程阻塞等待RWMutex、通道、调度休眠都依赖它。本文立足 GMP 模型细致拆解两者实现、作用场景、执行流程同时讲清二者如何配合完成 Goroutine 调度。前置约定 G (Goroutine) 用户协程M (OS 线程) 内核线程P (Processor) 调度上下文。一、操作系统信号 Signal实现异步抢占调度1. 什么是信号信号是 Linux/Unix 内核提供的异步软件中断。内核可以主动向一个线程发送信号强制打断线程当前执行流转入预先注册的信号处理函数。 Go 调度器利用SIGURG 信号实现协程抢占。2. 为什么需要信号抢占Go 1.14 之前只有协作式调度Goroutine 必须在函数调用、通道阻塞等位置主动让出 P。 如果存在纯 CPU 密集循环无任何函数调用G 会持续霸占 P其他协程饥饿。 Go 1.14 引入异步抢占依靠信号强制中断长时间运行的 G。3. 完整抢占流程GMP 联动sysmon监控线程周期性扫描所有 P判断某个 P 上正在运行的 G 连续运行超过10ms获取当前绑定的 M操作系统线程向该线程发送SIGURGOS 收到信号中断线程正常执行流进入 Go 预先注册的信号处理函数信号处理函数修改 G 的上下文打上抢占标记⚠️不会立刻切换协程Go 采用安全点机制必须等到 G 执行到下一个函数调用安全点G 检测抢占标记主动保存运行现场当前 G 暂停放回 P 的本地队列P 寻找下一个可运行 G 执行。4. 关键误区信号发给M操作系统线程不是发给 G信号只是 “通知标记”不能直接中断任意代码必须等待安全点信号抢占只解决CPU 密集协程霸占 P 的场景阻塞系统调用文件 IO、socket不会触发信号抢占走 P 与 M 解绑迁移逻辑。二、运行时信号量 Semaphore线程 / 协程休眠唤醒原语1. 信号量是什么Go runtime 内部实现的休眠原语不依赖操作系统信号。 信号量维护一个计数器提供两类操作acquire()计数器 0则计数器 - 1直接返回计数器 0当前 M 休眠阻塞release()计数器 1如果存在休眠的 M唤醒其中一个。标准库sync.RWMutex、sync.Mutex、channel 底层等待队列全部基于信号量实现。 RWMutex 内两个核心信号量readerSem等待读锁的协程唤醒信号量writerSem等待写锁的协程唤醒信号量2. 信号量在 GMP 中的典型场景场景 1写锁到来大量读协程被阻塞 读协程执行RLock()发现readerCount 0存在写操作调用acquire(readerSem)对应的 M 休眠等待信号量释放。场景 2写锁释放 执行Unlock()调用release(readerSem)唤醒所有等待读锁的 M读协程重新竞争锁。场景 3多个协程等待通道数据 没有数据时G 挂载到通道等待队列底层依靠信号量休眠对应 M写入数据后调用 release 唤醒等待的协程。3. 信号量休眠时 GMP 行为重点当 M 因为信号量休眠阻塞M 会和 P解绑P 不会闲置P 寻找其他 M 继续运行队列中的 GM 休眠在内核态等待信号量 release被唤醒后M 尝试重新绑定 P获取不到 P则把 G 放入全局队列M 进入休眠。三、核心对比信号 Signal vs 信号量 Semaphore表格维度操作系统信号 SignalSIGURG运行时信号量 Semaphore提供者操作系统内核Go runtime 用户态实现作用目标操作系统线程 M阻塞等待锁 /channel 的 M核心用途异步抢占长时间运行的 G协程阻塞休眠、条件唤醒触发方式主动发送中断异步通知主动 acquire 阻塞release 唤醒是否强制中断产生中断设置抢占标记依赖安全点切换不会中断代码主动进入休眠典型案例Go1.14 协程抢占调度RWMutex 读写等待、channel 等待队列四、一道经典面试串联题两种机制如何分工场景一个 P 上运行一个无限循环 CPU 密集 G同时有若干协程等待 RWMutex 读锁。CPU 密集 G 长时间占用 P → sysmon 发送SIGURG 信号触发抢占到达安全点后G 让出 P等待读锁的协程之前因写锁阻塞依靠readerSem 信号量唤醒总结分工信号负责「打断霸占 CPU 的协程」信号量负责「协程阻塞等待与条件唤醒」。二者互不替代是两套独立机制。五、常见面试问题汇总Q1信号抢占会触发 M 和 P 解绑吗不会。抢占属于用户态调度M 依旧绑定 P只是切换运行的 G。 只有系统调用阻塞、信号量休眠时才会发生 P 与 M 解绑。Q2信号量休眠和系统调用阻塞有区别吗有区别 信号量休眠Go runtime 主动调用操作系统休眠函数 系统调用阻塞M 主动进入内核等待 IO两套路径但都会触发 P 迁移。Q3可以用信号量实现抢占调度吗不行。信号量只能让协程主动休眠无法强制打断正在运行、不主动让出的 CPU 密集协程这也是 Go 必须引入信号抢占的根本原因。结语简单一句话记忆信号是内核中断用来强制抢占信号量是运行时休眠原语实现阻塞等待唤醒。理解两套机制就能打通抢占调度、RWMutex 原理、channel 等待队列、P-M 绑定迁移等整套 GMP 调度知识。