进程与调度
全库目录221
本文目录12
后台任务可以不阻塞界面,却未必同时占用多个核心。进程提供地址与故障隔离,线程由OS调度,协程与Task再在其上组织工作。系统调用、等待和切换各自影响延迟。
用户态、内核态与系统调用
普通代码运行在用户态,不能直接修改页表、操作设备或访问其他进程内存。需要文件、网络、线程和虚拟内存服务时,通过系统调用进入内核态。内核验证参数和权限,执行操作后返回用户态。
一次系统调用不等于一定发生线程切换,但进入内核、检查参数和处理设备会产生开销。高频小 IO、频繁创建线程和反复申请小块虚拟内存应通过批处理、缓存和用户态分配器减少系统调用次数。
句柄/文件描述符是进程访问内核对象的间接标识。它需要明确关闭,复制和跨进程继承也受权限控制。句柄泄漏会逐渐耗尽进程或系统资源,不能只用普通堆内存分析工具定位。
进程与线程
进程拥有独立虚拟地址空间、句柄表、安全上下文和一组线程。进程隔离让一个工具崩溃时不直接破坏另一个进程的内存,但进程间传递数据需要 IPC 和序列化。
线程是 CPU 调度的基本执行单元。一个进程内线程共享代码、堆、全局数据和多数句柄,每个线程拥有自己的寄存器、栈、线程局部存储和调度状态。共享地址空间让通信便宜,也使数据竞争成为主要风险。
协程、Fiber 和语言级 Task 是用户态调度抽象。它们可以在等待时主动让出执行权,但最终仍运行在一个或多个操作系统线程上。异步函数没有自动获得并行能力,CPU 密集任务只有被调度到其他可运行线程才可能并行。
调度与上下文切换
调度器根据线程状态、优先级、时间片和 CPU 亲和性选择运行者。线程可能处于 Running、Ready、Blocked 或 Sleeping 等状态。等待 Mutex、IO、事件时进入阻塞,CPU 可以运行其他线程。
上下文切换需要保存当前线程寄存器和执行位置,恢复另一个线程,并可能扰动缓存、TLB 和分支预测。切换成本与平台和工作集有关,不能只用固定纳秒数描述。
阻塞等待与忙轮询产生不同队列行为。任务提交后处于Ready,只有取得核心才进入Running;系统等待使它转为Blocked,条件满足后回到Ready而非立即Running。用户态协程挂起则把后续执行位置保存在任务对象中,由完成事件重新入队,工作线程可以处理其他任务。若线程池所有工作线程都同步等待排在同一池里的子任务,子任务没有执行者,即使操作系统仍正常调度也会死锁;任务依赖使用续体或允许工作线程帮助执行就绪任务,具体调度见任务系统。
线程数超过硬件并发度并不一定更快。大量短任务适合复用线程池,由任务系统控制粒度和依赖。实时线程可以提高优先级,但必须避免锁、分配和不可控 IO,否则高优先级阻塞会影响整个系统。
进程间通信
常见 IPC 包括 Pipe、Socket、共享内存、命名管道、消息队列和子进程标准输入输出。
- Pipe 和 Socket 边界清楚,容易隔离崩溃,代价是复制、协议和序列化;
- 共享内存适合大数据交换,但同步、所有权和版本必须由双方约定;
- 启动 CLI 子进程适合 DCC 与资产工具,输入输出应有结构约束、退出码、超时和取消;
- RPC 建立在传输和序列化之上,仍要处理超时、重复与进程失效。
跨进程不能传普通指针。即使两个进程映射同一物理内存,虚拟地址也可能不同,应使用相对偏移、索引或稳定ID。
共享内存传递对象时,例如生产者映射基址0x1000、消费者映射基址0x8000,记录的相对偏移32分别对应0x1020与0x8020。发布前写好负载与长度,再按同步协议发布就绪状态;只换成偏移不能保证读者不会看到半写负载,同步规则见 线程同步。
时间与计时器
墙钟表示日历时间,可能因系统校时或用户修改发生跳变。时区变化只改变本地日历表示,不改变同一时刻的UTC时间戳。测量持续时间和驱动游戏循环应使用单调时钟。
休眠接口的返回时刻受调度与中断语义影响,不能当作精确唤醒保证;是否允许提前返回要查看具体API契约。调度粒度、系统负载和省电策略都会产生误差。固定 Tick 应使用累计器和真实单调时间计算,不要依赖每次 Sleep 恰好一个帧间隔。
固定Tick可令累计器初值0、步长0.02秒。某次单调时间推进0.055秒,就执行两次模拟并留下0.015秒,下一次再推进0.01秒,执行一次并留下0.005秒。Sleep只用于减少等待时的忙轮询,唤醒后仍重新测时。长暂停若积累几十步,必须定义最大追赶次数以及剩余时间的处理,避免模拟追赶持续占满线程。
工具进程协议
伪代码输入结构化任务,标准输出返回结构化结果,退出码与超时独立记录。
start worker with versioned arguments
send input manifest
read bounded stdout and stderr
wait until completion or deadline
validate exit code and output manifest等待队列与子进程输出
线程正在运行、可运行却没得到CPU、正在等待外部条件,是三种不同状态。主线程卡顿时若大部分时间在等待,增加它的优先级不会让磁盘更快;若任务已经就绪却排队,则要检查CPU占用、优先级和线程池饱和。性能时间线需要把运行与等待拆开。
子进程的标准输出管道有容量。父进程先阻塞等待退出、子进程先写大量输出时,子进程可能等管道空位而无法退出,双方形成等待环。父进程在子进程运行期间持续排空stdout和stderr,并对保留日志设上限。日志截断只限制存储,读取仍持续到EOF。
输入也可能形成同样的等待环:父进程写满stdin等待子进程读取,子进程写满stdout等待父进程读取。输入写入和两个输出排空因此并行推进,写完一份任务后关闭stdin或发送明确的帧结束标记,让等待输入结束的工具继续。写入途中子进程退出时,将管道关闭作为任务失败的一部分收集,不在写线程里无限重试。
超时后取消子进程任务,等待进程结束并收集最终退出码,才能把工作目录交给下一个任务。若工具还会启动子进程,取消策略需覆盖整棵进程树;否则父进程退出不代表所有写文件的执行者已经消失。
时间预算与任务退出
固定Tick累计器必须给出过载规则。单机表现模拟可每轮最多执行5个0.02秒步;执行后若累计器仍不小于一步,就丢弃完整旧步,只保留小于0.02秒的余量,并记录丢弃时间。暂停1秒时因此至多追赶0.1秒,其余时间显式丢弃,系统不会陷入持续追赶。权威对局不能静默采用这种规则:它需要保留Tick序号并通过降低非关键工作、暂停接收或告警处理超载,见 对局服务器架构。
工具进程任务把截止时间建立在单调时钟上,启动时同时开始stdout和stderr读取。协议帧先给固定长度头,再给有上限的负载长度;不足一帧继续读,EOF时有残帧则报告协议截断,只有完整帧进入解析器。stderr只作日志,不能与stdout结构数据拼接。子进程返回零退出码仍要检查结果帧、输出文件哈希与任务ID;非零退出码则保留诊断,不发布部分产物。
达到截止时间后先请求协作取消,经过固定宽限时间仍未退出就终止任务所属进程组或Job Object,再等待整组退出并排空两个管道。后台后代若可以脱离该组,就不能使用“组已退出”作为工作目录可复用的依据;启动策略先限制这种脱离,或者为后代分别登记所有者。进程终止、管道EOF、输出完整和产物批准因此是四个不同条件。
验证方法
记录Running、Ready与Blocked时间,模拟子进程退出、超时及巨大输出。传输使用偏移、ID或序列化,进程间不传普通指针。测时使用单调时钟。
实现与证据
正文工具进程是协议伪代码,未启动实际DCC或测量调度延迟。时间线诊断按Running/Ready/Blocked区分,平台测量工具与目标进程需一起记录。
相关主题
参考资料
- Python subprocess:Popen.wait的管道死锁警告、communicate与超时处理。
- Microsoft:Processes and Threads:进程、线程与调度对象。