公司动态
C++学习之进程
本文面向后端开发、C 开发工从基础定义、调度原理、生命周期、特殊进程僵尸进程、孤儿进程多个维度系统讲解进程线程全套核心知识。一、进程到底是什么1.1 进程定义进程是操作系统进行资源分配的最小单位。当我们双击打开一个软件、运行一段编译好的可执行程序时磁盘上静态的程序文件被操作系统加载到内存内核为它分配内存空间、文件描述符、栈、堆、信号、权限、设备使用权等一系列运行资源这个正在运行的程序实例就是进程。 静态存放在硬盘里的.exe、可执行文件只是程序程序加载进内存运行才诞生进程一个程序可以同时启动多个进程实例比如多次打开 Chrome 浏览器就会生成多个浏览器进程。1.2 进程拥有的核心资源每个进程都有独立、隔离的资源体系主要包含以下部分独立虚拟地址空间操作系统通过虚拟内存机制给每个进程分配完整的虚拟地址区间不同进程的虚拟地址相互隔离。进程 A 不能随意读写进程 B 的内存天然实现内存隔离单个进程内存崩溃不会直接破坏其他进程的数据。进程控制块 PCB内核专门维护的数据结构存储进程 PID进程唯一编号、进程状态、优先级、内存映射信息、打开的文件列表、信号掩码、父进程 PID、资源统计信息内核依靠 PCB 管理、调度所有进程。文件描述符表进程打开的普通文件、socket 套接字、管道、设备都会分配文件描述符进程可以通过描述符读写各类 IO 资源。独立环境变量、工作目录、用户权限不同进程可以拥有不同的环境变量、运行路径、操作权限。堆、共享库、栈区进程内部划分不同内存区域分别用于动态内存申请、调用系统库、函数调用栈存储。1.3 进程的核心特点资源隔离性强进程之间默认不能互相访问内存跨进程传递数据必须依靠管道、消息队列、共享内存、Socket 这类进程间通信IPC手段创建、销毁、切换开销大创建进程需要内核分配整套内存、页表进程切换时需要刷新 TLB、更换页表CPU 开销较高容错性好单个进程崩溃操作系统回收该进程资源其他进程不受影响所以大型服务经常用多进程做故障隔离。二、线程到底是什么2.1 线程官方定义线程Thread是操作系统 CPU 调度、执行的最小单位线程不能脱离进程独立存在线程是寄生在进程内部的执行分支一个进程可以包含 1 个或者多个线程。我们日常编写的单线程程序进程内部就只有一条主线程。2.2 线程拥有的资源同一个进程内所有线程共享进程的绝大多数资源每个线程仅私有少量必要资源线程共享资源同一进程整个进程的虚拟地址空间、堆内存文件描述符、信号处理方式、工作目录、用户权限动态库、全局变量。线程私有资源独立的调用栈存储函数调用层级、局部变量线程上下文寄存器CPU 调度保存线程运行状态线程局部存储 TLS专属线程的私有变量线程状态、优先级。2.3 线程的核心特点切换成本极低线程切换只需要保存、恢复 CPU 寄存器不需要更换页表、刷新 TLB上下文切换开销远小于进程天然共享内存多个线程读写同一个全局变量就可以完成数据交互不需要复杂 IPC 通信开发并发逻辑更简便容错性弱一个线程触发未捕获的致命异常操作系统会直接销毁整个进程同进程内其余线程全部终止轻量创建上千条线程系统也可以承载但上千个进程会大量消耗内存、内核资源。三、进程和线程的关系、核心区别关系总结从属关系线程依附进程诞生、消亡进程是线程的容器进程关闭内部所有线程强制销毁线程不能脱离进程单独存在。资源分配与调度分工操作系统以进程为单位分配内存、文件等资源以线程为单位分配 CPU 时间片、调度执行代码。通信成本差异同进程线程依靠全局变量、互斥锁通信跨进程必须使用管道、共享内存、Socket 等 IPC。开销差异进程创建、切换、销毁开销大线程开销很小适合高频并发场景。对比维度进程线程资源属性资源分配最小单位资源独立CPU 调度最小单位共享进程资源内存隔离不同进程内存严格隔离同进程线程共享堆、全局内存创建开销很高内核要分配虚拟内存、页表很低只需创建 TCB、线程栈切换开销需要切换页表、刷新 TLBCPU 损耗大仅保存寄存器上下文开销很小崩溃影响单个进程崩溃其他进程不受影响单线程致命异常整个进程退出通信方式IPC管道、消息队列、共享内存、Socket全局变量、条件变量、互斥锁四、进程、线程调度机制CPU 核心同一时刻只能执行一段代码大量进程、线程争抢有限的 CPU 核心就需要操作系统内核的调度器分配 CPU 时间这套规则就是调度机制。4.1 调度器核心目标最大化 CPU 利用率避免 CPU 长期空闲兼顾响应速度网页请求、键盘鼠标交互这类任务需要快速得到 CPU降低延迟兼顾吞吐量后台批量计算任务单位时间完成更多运算避免饥饿防止低优先级任务永远得不到 CPU 执行机会。4.2 主流调度算法1. 时间片轮转调度 RR就绪队列里的线程轮流分配固定时长 CPU 时间片线程时间片用完后调度器强制剥夺 CPU线程回到就绪队列末尾调度下一个线程运行。 适用通用多任务场景保障所有任务都能分到 CPU缺点时间片太短频繁上下文切换消耗 CPU时间片太长交互任务卡顿。2. 抢占式优先级调度给每个线程、进程设置优先级CPU 优先运行高优先级任务如果 CPU 正在运行低优先级任务此时就绪队列来了更高优先级任务调度器立刻暂停低优先级任务把 CPU 交给高优先级任务。 典型应用Linux 实时线程、工业控制程序隐患大量高优先级任务持续占用 CPU低优先级任务长期无法执行出现饥饿内核会动态提升长时间未运行任务的优先级规避饥饿。3. 多级反馈队列调度Linux 普通进程默认调度策略融合时间片轮转、优先级调度设置多层就绪队列上层队列优先级高、时间片短新任务优先进入最高层队列IO 密集型任务经常等待网络、磁盘主动让出 CPU长期停留在高层队列响应迅速CPU 密集型任务会耗尽时间片下沉到下层队列获得更长时间片减少切换次数 该算法同时适配网络服务、后台计算两类任务是服务器系统主流调度方案。五、进程、线程完整生命周期5.1 进程五大生命周期状态创建态调用fork()、exec等系统调用创建子进程内核分配 PID、构建 PCB、申请虚拟内存尚未加入就绪队列。就绪态资源分配完成进程进入就绪队列等待调度器分配 CPU 时间片拿到 CPU 即可立刻运行。运行态调度器选中该进程内的线程CPU 执行进程代码逻辑。阻塞态等待态进程执行sleep、读取 socket、等待锁等阻塞操作主动放弃 CPU即便 CPU 空闲调度器也不会调度该进程等待的事件数据到达、锁释放完成后进程重新回到就绪态。终止态进程调用 exit 正常退出、被信号杀死内核释放绝大多数内存、文件资源但 PCB、退出码暂时保留等待父进程调用 wait 回收若父进程不回收就变成僵尸进程。5.2 线程五大生命周期状态新建态代码创建线程对象内核还未生成线程控制块 TCB。就绪态启动线程内核初始化 TCB线程加入调度队列等待 CPU 调度。运行态调度器分配时间片CPU 执行线程代码。阻塞态线程等待互斥锁、IO、条件变量、sleep暂时移出调度队列事件触发后回到就绪态。终止态线程执行结束、被终止分离态线程资源直接释放连接态线程需要主线程调用join回收资源否则造成内存泄漏。5.3 进程与线程生命周期联动进程作为容器销毁时内核强制结束内部所有线程若线程陷入死循环无法终止进程也无法正常退出单线程未捕获的致命异常会直接终结整个进程其余线程一并销毁这就是高可靠服务要做多线程隔离的重要原因。六、两类特殊进程僵尸进程、孤儿进程6.1 僵尸进程产生原理子进程先执行完毕退出内核释放子进程堆、文件等大部分资源但是进程控制块 PCB 必须由父进程回收如果父进程没有调用wait()、waitpid()获取子进程退出状态、回收 PCB子进程就会停留在进程表中状态标记为 Z僵尸也就是僵尸进程。 简单来说子进程肉身已经消亡只剩进程登记信息留在系统里等待父进程认领销毁父进程迟迟不来认领就成为僵尸进程。僵尸进程的危害每个进程占用唯一 PIDLinux 系统 PID 数量存在上限大量僵尸进程堆积会耗尽系统 PID新的程序无法创建进程服务直接故障占用内核进程表条目增加内核遍历进程的开销无法通过kill命令消灭僵尸进程僵尸进程已经不运行代码发送终止信号没有作用只能依靠父进程回收或者杀死父进程交由 init 进程回收。主流解决方案捕获 SIGCHLD 信号子进程退出时内核会向父进程发送 SIGCHLD 信号父进程注册信号处理函数在函数内部循环调用waitpid回收所有已退出子进程工业界最常用方案父进程同步等待创建子进程后立刻调用 wait 阻塞等待子进程退出适合子进程数量少的场景终止父进程若父进程已经废弃直接杀掉父进程僵尸进程会被 PID 为 1 的 systemd/init 进程收养并回收。6.2 孤儿进程产生原理父进程先退出终止它下属还在运行的子进程就成为孤儿进程Linux 内核会自动把所有孤儿进程过继给 init/systemdPID1进程收养后续孤儿进程运行结束由 init 进程负责调用 wait 回收 PCB 资源。孤儿进程的特点、风险系统层面无资源泄漏风险内核自动托管回收不会产生僵尸进程业务层面隐患极大父进程原本负责管控子进程任务、端口、文件父进程退出后子进程脱离原有业务链路在后台长期运行占用端口、读写文件、消耗内存引发端口占用、脏数据、后台资源持续消耗等问题例如父进程是 web 服务意外退出负责数据同步的子进程继续读写数据库会造成数据错乱。孤儿进程应对方案父子进程增加心跳通信父进程退出前主动向子进程发送终止信号让子进程同步退出使用守护进程托管业务把需要长期运行的任务交由 systemd 管理进程启动时写入 pid 文件运维可通过 pid 文件筛查遗留后台进程。七、线程间同步7.1 线程同步保护原因同一进程内所有线程共享进程全局堆、全局变量、静态变量仅各自拥有独立私有栈。 当多条线程同时读写同一份共享数据且无任何保护机制时CPU 多线程交替执行指令会破坏数据读写完整性出现脏数据、数值错乱、业务逻辑异常这是并发程序最常见 BUG且复现随机性极强线上偶发故障大多源于此因此必须依靠线程同步工具实现线程保护。7.2 线程同步手段7.2.1 互斥锁Mutex核心作用同一时间仅允许一条线程持有锁访问共享资源其余竞争线程阻塞等待从根源避免多线程同时写数据。适用场景读写操作次数均衡、简单共享变量保护计数、状态标记。优缺点实现简单、使用广泛读多写少场景下读线程互相阻塞性能损耗大。7.2.2 读写锁RwLock核心作用区分读、写权限读共享多条读线程可同时获取读锁并发读取数据写独占写线程获取写锁时阻塞所有读、写线程。适用场景读多写少业务配置缓存、静态数据、只读业务内存。优缺点高并发读场景性能远优于互斥锁频繁交替读写场景会产生写饥饿。7.2.3 条件变量核心作用不能单独使用必须搭配互斥锁用于线程等待特定业务条件达成后再执行逻辑实现线程间等待 / 唤醒通信。典型场景生产者消费者队列、任务池空队列等待、资源不足阻塞等待。工作流程线程加互斥锁 → 判断条件不满足 → 释放锁并阻塞休眠 → 其他线程修改条件后唤醒 → 重新获取锁校验条件并执行业务。7.2.4 自旋锁核心作用和互斥锁功能一致区别在于竞争锁失败时不进入内核阻塞休眠循环空转 CPU 持续轮询锁状态直到获取锁。适用场景临界区代码执行极短线程等待时间远小于内核切换阻塞开销内核开发、高并发低延迟基础组件。优缺点无上下文切换延迟长时间抢不到锁会持续占用 CPU 空转造成 CPU 资源浪费。7.2.5 信号量核心作用维护一个资源计数器限制同时访问共享资源的最大线程数量支持多线程同时持有资源。分类与场景二元信号量计数器 1功能等价互斥锁用于独占资源保护计数信号量计数器 N限流控制例如限制线程池最大并发、连接池最大连接数。7.3 线程同步工具选择总结读多写少缓存优先读写锁简单短临界区、高频轻量计算自旋锁生产者消费者、任务队列等待互斥锁 条件变量并发限流、资源池控制计数信号量通用均衡读写业务基础互斥锁。