多进程编程(典藏、含用例代码)

目录

进程

进程基础知识

1. 进程的定义

进程计算机中正在执行的程序的实例,它是操作系统进行资源分配和调度的基本单位。进程不仅包含程序的代码,还包括程序执行时所需的各种系统资源和管理信息。简单的理解,程序(可执行程序)是一个静态的概念,而进程是一个动态的概念,即进程是程序的一次运行过程。
进程是程序的一次动态执行过程,与静态的程序本身有本质区别:

  • 程序:静态的指令集合,存储在存储介质上
  • 进程:动态的执行实体,拥有生命周期(创建、就绪、运行、挂起、终止)

2. 进程的组成要素(进程映像)

一个完整的进程包含以下几个核心组成部分:

A. 代码段(Text Segment)
  • 存储可执行指令
  • 通常是只读的,防止程序自我修改
  • 包含程序的实际操作代码
B. 数据段(Data Segment)
  • 初始化数据段:全局变量、静态变量
  • 未初始化数据段(BSS):未初始化的全局变量
  • 堆(Heap):动态分配的内存区域
    • 通过 malloc()new 等分配
    • 手动管理,生命周期不确定
C. 栈(Stack)
  • 存储函数调用信息、局部变量、参数
  • 自动管理,后进先出(LIFO)结构
  • 包含:
    • 函数返回地址
    • 局部变量
    • 函数参数
    • 临时数据

3. 进程的特征

A. 动态性
  • 有生命周期:创建、就绪、运行、挂起、终止
  • 状态不断变化
B. 并发性
  • 多个进程可以同时存在于内存中
  • 宏观上同时运行,微观上交替执行
C. 独立性
  • 每个进程有独立的地址空间
  • 进程间互不干扰(除非显式通信)
D. 异步性
  • 进程以不可预知的速度推进
  • 需要同步机制协调
E. 结构性
  • 有明确的结构组成(代码、数据、栈、PCB)

进程的状态

进程状态包括创建、就绪、运行、阻塞\挂起、终止,以及状态之间的转换如下图所示:

创建 New
就绪 Ready
运行 Running
阻塞 Blocked
终止 Terminated
就绪挂起 Ready-Suspended
阻塞挂起 Blocked-Suspended

各状态详细说明

1. 创建 (New)
  • 含义:进程正在被创建,但尚未准备好执行
  • 资源分配:操作系统正在分配PCB、初始化资源
  • 持续时间:通常很短
2. 就绪 (Ready)
  • 含义:进程已准备好运行,等待CPU时间片
  • 条件:拥有除CPU外的所有必要资源
  • 队列:处于就绪队列中等待调度
3. 运行 (Running)
  • 含义:进程正在CPU上执行指令
  • 数量限制:单CPU系统中同一时刻只有一个进程处于此状态
  • 资源占用:正在使用CPU资源
4. 阻塞/挂起 (Blocked/Suspended)
  • 阻塞:等待某事件(如I/O完成、信号量)
  • 挂起:进程被换出到外存,暂时不参与调度
5. 终止 (Terminated)
  • 含义:进程已完成执行或被强制终止
  • 清理工作:操作系统回收资源,撤销PCB

状态转换原因和过程

转换1: 创建 → 就绪
  • 原因:操作系统完成进程初始化
  • 过程
    1. 分配PCB并初始化
    2. 分配内存空间
    3. 建立与其他进程的关系
    4. 加入就绪队列
转换2: 就绪 → 运行
  • 原因:进程调度器选择该进程执行
  • 过程
    1. 调度器从就绪队列选择进程
    2. 进行上下文切换
    3. 恢复进程的CPU状态
    4. 开始执行
转换3: 运行 → 就绪
  • 原因
    • 时间片用完
    • 更高优先级进程就绪
    • 被中断处理程序抢占
  • 过程
    1. 保存当前进程上下文
    2. 更新PCB状态为就绪
    3. 加入就绪队列末尾
    4. 调度其他进程
转换4: 运行 → 阻塞
  • 原因
    • 等待I/O操作完成
    • 请求系统资源不可用
    • 等待信号量或锁
    • 等待子进程结束
  • 过程
    1. 进程主动发起等待请求
    2. 保存进程上下文
    3. 更新PCB状态为阻塞
    4. 加入相应等待队列
转换5: 阻塞 → 就绪
  • 原因:等待的事件发生
  • 过程
    1. 事件完成(如I/O完成中断)
    2. 从阻塞队列移除
    3. 更新PCB状态为就绪
    4. 加入就绪队列
转换6: 运行 → 终止
  • 原因
    • 正常执行完成
    • 出现无法处理的错误
    • 被其他进程杀死
    • 父进程终止
  • 过程
    1. 释放所有分配的资源
    2. 通知父进程
    3. 从所有队列中移除
    4. 撤销PCB
转换7: 就绪 → 就绪挂起
  • 原因:内存不足,需要换出低优先级进程
  • 过程:进程被换出到外存,释放内存
转换8: 就绪挂起 → 就绪
  • 原因:内存可用,需要执行该进程
  • 过程:进程被换入内存
转换9: 阻塞 → 阻塞挂起
  • 原因:所有阻塞进程都可能被挂起以释放内存
  • 过程:进程被换出到外存
转换10: 阻塞挂起 → 阻塞
  • 原因:等待的事件发生,但内存不足
  • 过程:保持挂起状态,但事件已记录
转换11: 阻塞挂起 → 就绪挂起
  • 原因:等待的事件发生
  • 过程:状态改为就绪,但仍在外存中

进程的优缺点

进程是操作系统进行资源分配和调度的基本单位,是程序的一次执行实例。每个进程拥有独立的地址空间、文件描述符、环境变量等系统资源。

优点缺点
1. 强大的隔离性
• 独立地址空间,互不干扰
• 一个进程崩溃不影响其他进程
• 安全边界清晰
1. 创建和销毁开销大
• 需要分配完整的地址空间
• 资源初始化复杂
• 系统调用频繁
2. 稳定性高
• 错误不会扩散到其他进程
• 可独立重启崩溃的进程
• 系统整体稳定性好
2. 上下文切换成本高
• 需要切换页表、刷新TLB
• 缓存失效严重
• 性能开销大
3. 安全性好
• 进程间无法直接访问内存
• 操作系统提供保护机制
• 权限控制明确
3. 通信复杂低效
• 需要IPC机制(管道、消息队列等)
• 数据拷贝次数多
• 通信延迟高
4. 资源管理清晰
• 操作系统可精确统计资源使用
• 资源回收彻底
• 内存泄漏影响范围有限
4. 资源占用多
• 每个进程都有独立的资源副本
• 内存浪费严重
• 系统资源消耗大
5. 编程模型简单
• 单进程程序逻辑清晰
• 无需考虑复杂的同步问题
• 调试相对容易
5. 并发性能有限
• 难以充分利用多核CPU
• 进程间协调困难
• 扩展性差
6. 容错性强
• 可监控和管理每个进程
• 支持进程级别的热备
• 系统健壮性好
6. 启动速度慢
• 加载程序、初始化资源耗时
• 响应延迟高
• 不适合实时性要求高的场景
7. 跨平台一致性
• 进程模型在各操作系统间相对统一
• 可移植性好
• 行为可预测
7. 状态管理复杂
• 进程状态维护成本高
• 父子进程关系复杂
• 僵尸进程问题

进程控制块

操作系统为每个进程维护的数据结构,包含:

PCB 组件内容描述
进程标识符进程ID(PID)、父进程ID(PPID)
进程状态运行、就绪、阻塞、挂起等
CPU状态寄存器值、程序计数器、栈指针
调度信息优先级、调度队列指针
内存管理页表、段表、内存限制
记账信息CPU使用时间、时间限制
I/O状态打开文件列表、I/O设备分配

进程间调度与上下文切换

进程调度

进程调度是操作系统的核心功能,负责决定哪个进程在何时使用CPU资源。调度器需要平衡多个目标:公平性、响应性、吞吐量和系统效率。

进程调度层次
1. 长期调度(作业调度)
  • 频率:分钟级
  • 作用:决定哪些作业进入内存准备执行
  • 控制并发度:防止系统过载
2. 中期调度(内存调度)
  • 频率:秒级
  • 作用:在内存和磁盘间交换进程
  • 解决内存压力:挂起和激活进程
3. 短期调度(CPU调度)
  • 频率:毫秒级(频繁)
  • 作用:决定下一个运行的进程
  • 性能关键:直接影响系统响应性

上下文切换

上下文切换是保存当前运行进程的状态,并恢复另一个进程状态的过程,使得CPU可以从一个进程切换到另一个进程。

上下文切换的完整步骤
1. 触发条件
  • 时间片用完
  • I/O请求
  • 更高优先级进程就绪
  • 进程主动放弃CPU
  • 中断处理
2. 上下文切换详细流程
触发调度
保存当前进程上下文
更新当前进程PCB
选择下一个进程
更新内存管理
恢复新进程上下文
跳转到新进程
3. 详细技术实现
保存当前进程上下文
; 伪汇编代码示例 - 保存进程状态
context_save:
    pushad          ; 保存所有通用寄存器
    pushf           ; 保存标志寄存器
    mov [eax+PCB.esp], esp  ; 保存栈指针到PCB
    mov [eax+PCB.eip], eip  ; 保存指令指针到PCB
    ; 保存其他CPU状态...
进程控制块(PCB)结构
// PCB中保存的关键信息
typedef struct pcb {
    // 进程标识
    pid_t pid;
    pid_t ppid;
    
    // CPU状态
    uint32_t eax, ebx, ecx, edx;
    uint32_t esi, edi, esp, ebp;
    uint32_t eip;
    uint32_t eflags;
    
    // 内存管理
    uint32_t cr3;           // 页目录基址寄存器
    page_table_t* page_table;
    
    // 调度信息
    process_state_t state;
    int priority;
    uint64_t time_remaining;
    
    // 资源信息
    file_descriptor_t* open_files;
    uint32_t working_directory;
    
    // 统计信息
    uint64_t cpu_time_used;
    uint64_t start_time;
} pcb_t;
内存管理单元切换
// 切换地址空间
void switch_address_space(pcb_t* next_process) {
    // 1. 刷新TLB(Translation Lookaside Buffer)
    flush_tlb();
    
    // 2. 切换页表
    write_cr3(next_process->cr3);
    
    // 3. 更新内存管理数据结构
    current_page_table = next_process->page_table;
}
恢复新进程上下文
; 伪汇编代码示例 - 恢复进程状态
context_restore:
    mov esp, [ebx+PCB.esp]  ; 恢复栈指针
    mov cr3, [ebx+PCB.cr3]  ; 切换地址空间
    ; 恢复其他寄存器...
    popf                    ; 恢复标志寄存器
    popad                   ; 恢复通用寄存器
    jmp [ebx+PCB.eip]       ; 跳转到保存的指令地址

调度算法详解

1. 先来先服务(FCFS)
// 简单队列实现
typedef struct {
    pcb_t* queue[MAX_PROCESSES];
    int front, rear;
} fcfs_queue_t;

void fcfs_schedule(fcfs_queue_t* q) {
    if (q->front <= q->rear) {
        pcb_t* next = q->queue[q->front++];
        switch_to_process(next);
    }
}

特点:非抢占式,简单但可能导致护航效应

2. 最短作业优先(SJF)
// 按预计运行时间排序
void sjf_schedule(pcb_t** ready_queue, int count) {
    // 按预计运行时间排序
    qsort(ready_queue, count, sizeof(pcb_t*), compare_burst_time);
    
    // 选择预计运行时间最短的进程
    pcb_t* next = ready_queue[0];
    switch_to_process(next);
}
3. 优先级调度
typedef struct {
    pcb_t* process;
    int priority;
} priority_item_t;

void priority_schedule(priority_queue_t* pq) {
    priority_item_t* highest = pq->extract_max();
    if (highest) {
        switch_to_process(highest->process);
    }
}
4. 轮转调度(RR)
// 环形队列实现轮转
typedef struct {
    pcb_t* processes[MAX_PROCESSES];
    int front, rear;
    int time_quantum;
} rr_queue_t;

void rr_schedule(rr_queue_t* q) {
    pcb_t* current = q->processes[q->front];
    
    if (current->time_remaining > 0) {
        // 进程还有时间,重新入队
        q->rear = (q->rear + 1) % MAX_PROCESSES;
        q->processes[q->rear] = current;
    }
    
    // 选择下一个进程
    q->front = (q->front + 1) % MAX_PROCESSES;
    pcb_t* next = q->processes[q->front];
    switch_to_process(next);
}
5. 多级反馈队列(MLFQ)
typedef struct {
    rr_queue_t queues[NUM_PRIORITY_LEVELS];
    int time_quantums[NUM_PRIORITY_LEVELS];
} mlfq_scheduler_t;

void mlfq_schedule(mlfq_scheduler_t* scheduler) {
    // 从最高优先级队列开始检查
    for (int i = 0; i < NUM_PRIORITY_LEVELS; i++) {
        if (!is_empty(&scheduler->queues[i])) {
            pcb_t* next = rr_schedule_from_queue(&scheduler->queues[i]);
            
            // 如果进程用完时间片还没结束,降低优先级
            if (next->time_used >= scheduler->time_quantums[i] && 
                next->state != TERMINATED) {
                demote_process(next, i + 1);
            }
            
            switch_to_process(next);
            return;
        }
    }
}

减少不必要的上下文切换
  1. 使用线程代替进程:减少地址空间切换
  2. 调整调度参数:合理设置时间片大小
  3. 优化I/O操作:使用异步I/O减少阻塞
  4. 进程亲和性:将进程绑定到特定CPU核心

多进程编程

多进程编程是一种并发编程范式,通过创建多个独立的进程来同时执行多个任务。每个进程拥有自己独立的地址空间、文件描述符、环境变量等系统资源,操作系统负责进程间的调度和资源管理。

多进程编程优缺点对比表格

优点缺点
1. 强大的隔离性
• 独立地址空间,进程间互不干扰
• 一个进程崩溃不会影响其他进程
• 内存泄漏仅限于单个进程
1. 创建和销毁开销大
• 需要分配完整的地址空间
• 资源初始化复杂耗时
• 系统调用频繁,性能开销大
2. 高稳定性和容错性
• 单个进程失败不影响系统整体
• 可独立重启崩溃的进程
• 错误隔离性好
2. 上下文切换成本高
• 需要切换页表、刷新TLB
• 缓存失效严重
• 切换时间比线程长5-10倍
3. 安全性好
• 进程间无法直接访问内存
• 操作系统提供硬件级保护
• 权限控制机制完善
3. 进程间通信(IPC)复杂
• 需要专门的IPC机制
• 数据序列化和拷贝开销大
• 通信延迟高,编程复杂
4. 简化编程模型
• 单进程逻辑清晰简单
• 无需考虑复杂的同步问题
• 调试相对容易
4. 资源占用多
• 每个进程都有独立资源副本
• 内存浪费严重
• 系统资源消耗大
5. 更好的CPU利用率
• 真正利用多核CPU并行执行
• I/O阻塞时不浪费CPU时间
• 适合计算密集型任务
5. 启动速度慢
• 加载程序、初始化资源耗时
• 响应延迟高
• 不适合实时性要求高的场景
6. 清晰的资源管理
• 操作系统可精确监控资源使用
• 资源回收彻底
• 资源统计准确
6. 状态管理复杂
• 进程状态维护成本高
• 父子进程关系复杂
• 僵尸进程和孤儿进程问题
7. 跨平台一致性
• 进程模型在各系统间相对统一
• 可移植性好
• 行为可预测性强
7. 扩展性限制
• 进程数量受系统限制
• 太多进程导致性能下降
• 难以动态调整规模

适合多进程的场景

  1. 需要强隔离性的应用

    • Web浏览器(每个标签页独立进程)
    • 数据库连接池
    • 安全敏感服务
  2. 计算密集型任务

    • 科学计算
    • 图像/视频处理
    • 大数据分析
  3. 独立服务组件

    • 微服务架构
    • 系统守护进程
    • 批处理任务

不适合多进程的场景

  1. 高并发网络服务器

    • 每个连接一个进程开销太大
    • 推荐使用线程池或异步I/O
  2. 实时性要求高的应用

    • 游戏引擎
    • 实时音视频处理
    • 高频交易系统
  3. 资源受限环境

    • 嵌入式系统
    • 移动设备应用
    • 低功耗设备

多进程编程是一种强大但重量级的并发解决方案。在选择多进程时应该考虑:

  1. 隔离性需求:是否需要进程级别的错误隔离
  2. 资源条件:系统是否有足够资源支持多进程
  3. 性能要求:是否可以接受进程创建和IPC的开销
  4. 开发复杂度:是否有能力处理复杂的进程管理和通信

现代系统通常采用混合架构,在需要强隔离的组件间使用进程,在组件内部使用线程处理并发,充分发挥各自的优势。

多进程创建

进程创建的主要方法

在多进程编程中,主要有以下几种创建进程的方法:

方法特点适用场景
fork()创建当前进程的副本,父子进程共享代码但拥有独立数据空间通用的进程复制,最常用
vfork()更轻量的fork,父子进程暂时共享地址空间子进程立即exec的情况
exec系列用新程序替换当前进程映像执行外部程序
fork()+exec组合创建新进程并执行新程序执行外部程序的标准方式
posix_spawn()创建进程并设置属性的高级接口需要精细控制进程属性
system()执行shell命令简单的命令执行

详细方法和代码示例

1. fork() - 最基本的进程创建

特点:

  • 创建调用进程的完整副本
  • 子进程继承父进程的代码、数据、堆栈、文件描述符等
  • 写时复制(Copy-on-Write)技术优化内存使用
  • 返回两次:父进程返回子进程PID,子进程返回0
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

void fork_example() {
    printf("父进程开始,PID: %d\n", getpid());
    
    int shared_var = 100;  // 父子进程各自有独立的副本
    
    pid_t pid = fork();
    
    if (pid == -1) {
        perror("fork失败");
        return;
    }
    
    if (pid == 0) {
        // 子进程代码
        printf("子进程: PID=%d, 父进程PID=%d\n", getpid(), getppid());
        shared_var = 200;  // 修改的是子进程的副本
        printf("子进程: shared_var=%d\n", shared_var);
        _exit(0);  // 子进程退出
    } else {
        // 父进程代码
        printf("父进程: 创建了子进程PID=%d\n", pid);
        printf("父进程: shared_var=%d\n", shared_var);  // 仍然是100
        wait(NULL);  // 等待子进程结束
        printf("父进程: 子进程已结束\n");
    }
}
2. vfork() - 轻量级进程创建

特点:

  • 创建进程但不复制页表
  • 子进程与父进程共享地址空间
  • 子进程必须先调用exec或exit
  • 性能比fork更好,但使用限制多
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

void vfork_example() {
    printf("vfork示例开始\n");
    
    int stack_var = 50;  // 栈变量
    
    pid_t pid = vfork();  // 使用vfork
    
    if (pid == -1) {
        perror("vfork失败");
        return;
    }
    
    if (pid == 0) {
        // 子进程 - 与父进程共享地址空间
        printf("子进程修改前: stack_var=%d\n", stack_var);
        stack_var = 100;  // 这会直接影响父进程!
        printf("子进程修改后: stack_var=%d\n", stack_var);
        
        // vfork创建的子进程必须调用exec或_exit
        _exit(0);  // 使用_exit避免刷新stdio缓冲区
    } else {
        // 父进程
        wait(NULL);
        printf("父进程: stack_var=%d (被子进程修改)\n", stack_var);
    }
}
3. exec系列函数 - 程序执行

exec函数族:

  • execl() - 参数列表
  • execv() - 参数数组
  • execlp() - 在PATH中查找程序
  • execvp() - 在PATH中查找程序+参数数组
  • execle() - 带环境变量
  • execve() - 系统调用
#include <stdio.h>
#include <unistd.h>

void exec_example() {
    printf("当前进程PID: %d\n", getpid());
    
    // 方法1: execl - 参数列表
    printf("执行ls命令...\n");
    execl("/bin/ls", "ls", "-l", "-a", NULL);
    
    // 如果exec成功,后面的代码不会执行
    perror("exec失败");
}

void exec_with_path_example() {
    // 方法2: execlp - 在PATH中查找程序
    execlp("ps", "ps", "aux", NULL);
    perror("execlp失败");
}

void exec_with_env_example() {
    // 方法3: execle - 带自定义环境变量
    char *envp[] = {"MY_VAR=hello", "PATH=/bin:/usr/bin", NULL};
    execle("/bin/echo", "echo", "环境变量测试", NULL, envp);
    perror("execle失败");
}
4. fork() + exec组合 - 标准模式

特点:

  • 最常用的进程创建模式
  • fork创建新进程,exec加载新程序
  • 结合了两者的优点
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

void fork_exec_example() {
    printf("fork+exec组合示例\n");
    
    pid_t pid = fork();
    
    if (pid == -1) {
        perror("fork失败");
        return;
    }
    
    if (pid == 0) {
        // 子进程执行新程序
        printf("子进程准备执行date命令\n");
        execl("/bin/date", "date", NULL);
        
        // 如果exec失败才执行到这里
        perror("exec失败");
        _exit(1);
    } else {
        // 父进程等待子进程结束
        int status;
        waitpid(pid, &status, 0);
        
        if (WIFEXITED(status)) {
            printf("子进程正常退出,退出码: %d\n", WEXITSTATUS(status));
        } else {
            printf("子进程异常退出\n");
        }
    }
}

// 更复杂的例子:执行带参数的程序
void complex_fork_exec() {
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程执行grep命令
        char *args[] = {"grep", "main", "*.c", NULL};
        execvp("grep", args);
        perror("execvp失败");
        _exit(1);
    } else {
        wait(NULL);
        printf("grep命令执行完成\n");
    }
}
5. system() - 执行shell命令

特点:

  • 简单的命令行执行
  • 内部使用fork+exec实现
  • 阻塞等待命令完成
#include <stdio.h>
#include <stdlib.h>

void system_example() {
    printf("system函数示例\n");
    
    // 执行简单的shell命令
    int result = system("ls -l | head -5");
    
    if (result == -1) {
        perror("system失败");
    } else {
        printf("命令退出状态: %d\n", WEXITSTATUS(result));
    }
    
    // 执行复杂的管道命令
    system("ps aux | grep $$ | grep -v grep");
}
6. posix_spawn() - 高级进程创建

特点:

  • POSIX标准的高级接口
  • 可以精细控制进程属性
  • 比fork+exec更高效
#include <stdio.h>
#include <spawn.h>
#include <sys/wait.h>

extern char **environ;  // 环境变量

void posix_spawn_example() {
    printf("posix_spawn示例\n");
    
    pid_t pid;
    char *argv[] = {"ls", "-l", "-h", NULL};
    
    // 使用posix_spawn创建进程
    int result = posix_spawn(&pid, "/bin/ls", NULL, NULL, argv, environ);
    
    if (result != 0) {
        perror("posix_spawn失败");
        return;
    }
    
    printf("创建的子进程PID: %d\n", pid);
    
    // 等待子进程结束
    int status;
    waitpid(pid, &status, 0);
    printf("子进程执行完成\n");
}
综合比较表格
方法性能灵活性安全性可移植性使用难度
fork()中等(写时复制)所有Unix系统简单
vfork()低(共享地址空间)多数Unix系统中等
exec系列高(替换当前进程)所有Unix系统简单
fork()+exec中等所有Unix系统中等
system()低(需要shell)中(shell注入风险)所有Unix系统非常简单
posix_spawn()POSIX系统复杂

实际应用建议

选择指南:
  1. 简单任务复制:使用 fork()
  2. 执行外部命令:使用 fork() + exec
  3. 性能关键且子进程立即exec:考虑 vfork()
  4. 执行shell命令:使用 system()(注意安全)
  5. 需要精细控制:使用 posix_spawn()
安全注意事项:
// 不安全的system使用
system("rm -rf /user/files/" + user_input);  // 危险!

// 安全的替代方案
void safe_command_execution(const char* filename) {
    // 验证输入
    if (strstr(filename, "..") || strchr(filename, '/')) {
        fprintf(stderr, "无效文件名\n");
        return;
    }
    
    // 使用execl避免shell注入
    execl("/bin/rm", "rm", "-f", filename, NULL);
}
错误处理最佳实践:
pid_t pid = fork();

if (pid == -1) {
    // fork失败处理
    perror("fork失败");
    switch (errno) {
        case EAGAIN:
            printf("系统进程数达到限制\n");
            break;
        case ENOMEM:
            printf("内存不足\n");
            break;
        default:
            printf("未知错误\n");
    }
    return;
}

if (pid == 0) {
    // 子进程代码
    execl("/bin/ls", "ls", NULL);
    perror("exec失败");  // 只有exec失败时才执行
    _exit(127);  // 使用_exit避免刷新父进程的缓冲区
} else {
    // 父进程代码
    int status;
    if (waitpid(pid, &status, 0) == -1) {
        perror("waitpid失败");
    } else {
        if (WIFEXITED(status)) {
            printf("子进程退出码: %d\n", WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            printf("子进程被信号终止: %d\n", WTERMSIG(status));
        }
    }
}

不同的进程创建方法各有优缺点,选择合适的方法取决于具体需求:

  • 学习和简单应用:从 fork()system() 开始
  • 生产环境:推荐使用 fork() + exec 组合
  • 高性能需求:考虑 posix_spawn() 或特定场景下的 vfork()
  • 安全敏感:避免直接使用 system() 处理用户输入

多进程结束与资源回收

进程(结束)退出方式

1. 正常退出方式
A. 从main函数返回
#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("程序正常结束\n");
    return 0;  // 等价于 exit(0)
}
B. 调用exit()函数
#include <stdio.h>
#include <stdlib.h>

void exit_example() {
    printf("准备退出程序\n");
    
    // 刷新所有缓冲区,调用atexit注册的函数
    exit(EXIT_SUCCESS);  // 正常退出
    // 或者 exit(EXIT_FAILURE);  // 异常退出
}
C. 调用_exit()或_Exit()
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void _exit_example() {
    printf("这条消息不会被显示");  // 没有换行符,缓冲区未刷新
    
    // 立即退出,不刷新缓冲区,不调用atexit函数
    _exit(0);      // POSIX系统调用
    // 或 _Exit(0);  // C99标准
}
2. 异常退出方式
A. 被信号终止
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void signal_exit_example() {
    printf("进程PID: %d\n", getpid());
    
    // 发送信号终止自己
    raise(SIGTERM);  // 优雅终止
    // 或 raise(SIGKILL);  // 强制终止(不可捕获)
    
    printf("这行不会执行\n");
}
B. 调用abort()
#include <stdio.h>
#include <stdlib.h>

void abort_example() {
    printf("准备调用abort()\n");
    
    // 产生SIGABRT信号,通常导致核心转储
    abort();
    
    printf("这行不会执行\n");
}

退出状态码

标准退出码含义
#include <stdio.h>
#include <stdlib.h>

void exit_codes_example() {
    int result = some_operation();
    
    if (result == 0) {
        exit(EXIT_SUCCESS);  // 成功,通常为0
    } else if (result == -1) {
        exit(EXIT_FAILURE);  // 失败,通常为1
    } else if (result == 2) {
        exit(2);  // 自定义错误码
    }
}

// 常见的退出码约定:
// 0   - 成功
// 1   - 一般错误
// 2   - 命令行用法错误
// 126 - 命令不可执行
// 127 - 命令未找到
// 128+ - 信号终止(128 + 信号编号)

资源回收机制

1. 僵尸进程问题
什么是僵尸进程

僵尸进程(Zombie Process)是已经终止但仍然保留在进程表中,等待父进程读取其终止状态的进程。僵尸进程会占用系统资源,但不会影响程序运行。简单的说,僵尸进程是已经死掉的进程的残留信息,父进程需要通过调用wait()或waitpid()来回收这些信息。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void zombie_example() {
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程立即退出,成为僵尸
        printf("子进程PID: %d 退出\n", getpid());
        exit(0);
    } else {
        // 父进程不调用wait,子进程成为僵尸
        printf("父进程继续运行,子进程PID: %d 将成为僵尸\n", pid);
        sleep(30);  // 在此期间检查进程状态
        printf("父进程退出\n");
    }
}
2. 等待子进程退出
A. wait() - 等待任意子进程
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

void wait_example() {
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程
        printf("子进程工作...\n");
        sleep(2);
        printf("子进程结束\n");
        exit(42);  // 自定义退出码
    } else {
        // 父进程
        printf("父进程等待子进程...\n");
        
        int status;
        pid_t finished_pid = wait(&status);
        
        if (finished_pid == -1) {
            perror("wait失败");
            return;
        }
        
        printf("子进程 %d 已结束\n", finished_pid);
        
        if (WIFEXITED(status)) {
            printf("正常退出,退出码: %d\n", WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            printf("被信号终止,信号: %d\n", WTERMSIG(status));
        }
    }
}
B. waitpid() - 等待特定子进程
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

void waitpid_example() {
    pid_t children[3];
    
    // 创建多个子进程
    for (int i = 0; i < 3; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程
            printf("子进程 %d 开始,PID: %d\n", i, getpid());
            sleep(i + 1);  // 不同的睡眠时间
            printf("子进程 %d 结束\n", i);
            exit(i);  // 不同的退出码
        } else {
            children[i] = pid;
        }
    }
    
    // 父进程等待特定子进程
    for (int i = 0; i < 3; i++) {
        int status;
        
        // 阻塞等待特定子进程
        pid_t finished_pid = waitpid(children[i], &status, 0);
        
        if (WIFEXITED(status)) {
            printf("子进程 %d (PID: %d) 退出码: %d\n", 
                   i, finished_pid, WEXITSTATUS(status));
        }
    }
}
C. 非阻塞等待
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

void nonblocking_wait_example() {
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程长时间运行
        printf("子进程开始长时间任务...\n");
        sleep(10);
        exit(0);
    } else {
        // 父进程非阻塞等待
        int status;
        
        while (1) {
            pid_t result = waitpid(pid, &status, WNOHANG);
            
            if (result == 0) {
                // 子进程还在运行
                printf("子进程仍在运行,父进程做其他工作...\n");
                sleep(1);
            } else if (result == pid) {
                // 子进程已结束
                printf("子进程已结束\n");
                break;
            } else {
                // 错误
                perror("waitpid错误");
                break;
            }
        }
    }
}
3. 信号处理与子进程回收
使用SIGCHLD信号
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdlib.h>

// SIGCHLD信号处理函数
void sigchld_handler(int sig) {
    int status;
    pid_t pid;
    
    // 循环回收所有已结束的子进程
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        if (WIFEXITED(status)) {
            printf("信号处理: 子进程 %d 正常退出,退出码: %d\n", 
                   pid, WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            printf("信号处理: 子进程 %d 被信号 %d 终止\n", 
                   pid, WTERMSIG(status));
        }
    }
}

void sigchld_example() {
    // 注册SIGCHLD信号处理函数
    struct sigaction sa;
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
    
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction");
        return;
    }
    
    // 创建多个子进程
    for (int i = 0; i < 3; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            printf("子进程 %d 开始\n", getpid());
            sleep(i + 1);
            exit(i);
        }
    }
    
    // 父进程继续工作
    printf("父进程继续工作,子进程结束时会自动回收\n");
    for (int i = 0; i < 10; i++) {
        printf("父进程工作... %d\n", i);
        sleep(1);
    }
}

资源清理常用方法

1. 退出处理函数
使用atexit()
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void cleanup_temp_files() {
    printf("清理临时文件...\n");
    // 实际代码中删除临时文件
}

void close_database_connections() {
    printf("关闭数据库连接...\n");
    // 实际代码中关闭数据库
}

void release_memory() {
    printf("释放内存资源...\n");
    // 实际代码中释放动态内存
}

void atexit_example() {
    // 注册退出处理函数(逆序执行)
    atexit(release_memory);
    atexit(close_database_connections);
    atexit(cleanup_temp_files);
    
    printf("主程序运行...\n");
    
    // 正常退出时会自动调用注册的函数
    // exit(0);
}
使用on_exit()(Linux特有)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void exit_handler(int status, void *arg) {
    printf("退出处理: 状态=%d, 参数=%s\n", status, (char*)arg);
}

void on_exit_example() {
    // 注册退出处理函数
    on_exit(exit_handler, "自定义参数");
    
    printf("程序运行中...\n");
    exit(42);  // 退出时会调用exit_handler
}
2. 文件描述符清理
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

void file_cleanup_example() {
    int fd1, fd2;
    
    // 打开文件
    fd1 = open("file1.txt", O_CREAT | O_WRONLY, 0644);
    fd2 = open("file2.txt", O_CREAT | O_WRONLY, 0644);
    
    if (fd1 == -1 || fd2 == -1) {
        perror("打开文件失败");
        // 清理已打开的文件描述符
        if (fd1 != -1) close(fd1);
        if (fd2 != -1) close(fd2);
        exit(1);
    }
    
    // 工作代码...
    
    // 正常退出前清理
    close(fd1);
    close(fd2);
    
    // 或者让系统自动关闭(不推荐用于重要资源)
}
3. 避免资源泄漏的模式
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

void robust_process_management() {
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程代码
        // 设置资源限制
        struct rlimit lim;
        lim.rlim_cur = 100;  // 软限制
        lim.rlim_max = 200;  // 硬限制
        setrlimit(RLIMIT_CPU, &lim);
        
        // 子进程工作
        printf("子进程工作...\n");
        sleep(2);
        
        // 子进程显式清理
        printf("子进程清理资源...\n");
        exit(0);
    } else {
        // 父进程代码
        int status;
        
        // 设置超时等待
        for (int i = 0; i < 5; i++) {
            pid_t result = waitpid(pid, &status, WNOHANG);
            
            if (result == pid) {
                // 子进程正常结束
                printf("子进程正常结束\n");
                break;
            } else if (result == 0 && i == 4) {
                // 超时,强制终止子进程
                printf("子进程超时,强制终止\n");
                kill(pid, SIGKILL);
                waitpid(pid, &status, 0);  // 等待确认终止
            } else {
                sleep(1);
            }
        }
        
        // 检查终止原因
        if (WIFEXITED(status)) {
            printf("退出码: %d\n", WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            printf("被信号终止: %d\n", WTERMSIG(status));
        }
    }
}

特殊情况的处理

1. 孤儿进程处理
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void orphan_process_example() {
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程让父进程先退出
        sleep(1);
        printf("子进程PID: %d, 新的父进程PID: %d (init)\n", 
               getpid(), getppid());
        exit(0);
    } else {
        // 父进程立即退出
        printf("父进程退出,子进程PID: %d 将成为孤儿\n", pid);
        exit(0);
    }
}
2. 进程组和会话管理
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>

void process_group_example() {
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子创建新的进程组
        setpgid(0, 0);
        printf("子进程在新进程组中\n");
        
        // 工作...
        sleep(10);
        exit(0);
    } else {
        // 父进程可以终止整个进程组
        sleep(1);
        printf("父进程终止整个进程组\n");
        kill(-pid, SIGTERM);  // 负号表示进程组
        
        int status;
        waitpid(pid, &status, 0);
    }
}

总结表格

退出方式特点适用场景
return from main自动调用exit()简单程序
exit()刷新缓冲区,调用atexit函数标准退出方式
_exit()/_Exit()立即退出,不清理子进程避免干扰父进程
信号终止可能产生核心转储调试或异常处理
abort()产生SIGABRT信号严重错误情况
回收方法特点适用场景
wait()阻塞等待任意子进程简单子进程管理
waitpid()可指定进程,可非阻塞精确控制,多子进程
SIGCHLD信号异步通知避免阻塞父进程
WNOHANG非阻塞检查父进程需要继续工作

实践总结

  1. 总是检查系统调用返回值
  2. 使用wait()或waitpid()回收子进程
  3. 注册退出处理函数清理资源
  4. 处理SIGCHLD信号避免僵尸进程
  5. 为长时间运行的子进程设置超时
  6. 适当使用进程组管理相关进程

进程间通信

进程间通信(Inter-Process Communication, IPC) 是指在不同进程之间传播或交换信息的技术。由于每个进程都有自己独立的地址空间,一个进程不能直接访问另一个进程的变量和数据结构,因此需要操作系统提供特殊的机制来实现进程间的数据共享和通信。

IPC的主要目的

  1. 数据传输:一个进程需要将数据发送给另一个进程
  2. 资源共享:多个进程共享相同的资源
  3. 通知事件:一个进程需要向另一个进程通知某事件的发生
  4. 进程控制:有些进程希望控制另一个进程的执行

1. 无名管道(Unnamed Pipe)

特点

  • 只能用于具有亲缘关系的进程(如父子进程)
  • 半双工通信(数据只能单向流动)
  • 存在于内存中,随进程的结束而销毁
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

void unnamed_pipe_example() {
    int pipefd[2];
    pid_t pid;
    char buffer[100];
    
    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        return;
    }
    
    pid = fork();
    if (pid == -1) {
        perror("fork");
        return;
    }
    
    if (pid == 0) {
        // 子进程 - 读取数据
        close(pipefd[1]);  // 关闭写端
        
        ssize_t count = read(pipefd[0], buffer, sizeof(buffer));
        if (count > 0) {
            printf("子进程收到: %s\n", buffer);
        }
        
        close(pipefd[0]);
        _exit(0);
    } else {
        // 父进程 - 写入数据
        close(pipefd[0]);  // 关闭读端
        
        const char *message = "Hello from parent process!";
        write(pipefd[1], message, strlen(message) + 1);
        
        close(pipefd[1]);
        wait(NULL);  // 等待子进程结束
        printf("父进程: 数据已发送\n");
    }
}

// 双向通信示例
void bidirectional_pipe_example() {
    int pipe1[2], pipe2[2];
    pid_t pid;
    char buffer[100];
    
    // 创建两个管道实现双向通信
    if (pipe(pipe1) == -1 || pipe(pipe2) == -1) {
        perror("pipe");
        return;
    }
    
    pid = fork();
    if (pid == 0) {
        // 子进程
        close(pipe1[1]);  // 关闭pipe1的写端
        close(pipe2[0]);  // 关闭pipe2的读端
        
        // 从父进程读取
        read(pipe1[0], buffer, sizeof(buffer));
        printf("子进程收到: %s\n", buffer);
        
        // 向父进程发送
        const char *reply = "Hello from child!";
        write(pipe2[1], reply, strlen(reply) + 1);
        
        close(pipe1[0]);
        close(pipe2[1]);
        _exit(0);
    } else {
        // 父进程
        close(pipe1[0]);  // 关闭pipe1的读端
        close(pipe2[1]);  // 关闭pipe2的写端
        
        // 向子进程发送
        const char *message = "Hello from parent!";
        write(pipe1[1], message, strlen(message) + 1);
        
        // 从子进程读取
        read(pipe2[0], buffer, sizeof(buffer));
        printf("父进程收到回复: %s\n", buffer);
        
        close(pipe1[1]);
        close(pipe2[0]);
        wait(NULL);
    }
}

2. 有名管道(Named Pipe / FIFO)

特点

  • 可用于无亲缘关系的进程
  • 在文件系统中有一个路径名
  • 持久化直到被显式删除

写入进程 (fifo_writer.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define FIFO_PATH "/tmp/my_fifo"

void fifo_writer() {
    int fd;
    char message[] = "Hello from FIFO writer!";
    
    // 创建FIFO(如果不存在)
    if (mkfifo(FIFO_PATH, 0666) == -1) {
        // 如果FIFO已存在,可以忽略这个错误
        if (errno != EEXIST) {
            perror("mkfifo");
            return;
        }
    }
    
    printf("写入进程: 等待读取进程连接...\n");
    
    // 打开FIFO进行写入(会阻塞直到有进程打开读取)
    fd = open(FIFO_PATH, O_WRONLY);
    if (fd == -1) {
        perror("open");
        return;
    }
    
    // 写入数据
    if (write(fd, message, strlen(message) + 1) == -1) {
        perror("write");
    } else {
        printf("写入进程: 数据已发送\n");
    }
    
    close(fd);
    
    // 删除FIFO文件(可选)
    unlink(FIFO_PATH);
}

读取进程 (fifo_reader.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define FIFO_PATH "/tmp/my_fifo"

void fifo_reader() {
    int fd;
    char buffer[100];
    
    // 确保FIFO存在
    if (access(FIFO_PATH, F_OK) == -1) {
        printf("FIFO不存在,请先运行写入进程\n");
        return;
    }
    
    printf("读取进程: 打开FIFO...\n");
    
    // 打开FIFO进行读取
    fd = open(FIFO_PATH, O_RDONLY);
    if (fd == -1) {
        perror("open");
        return;
    }
    
    // 读取数据
    ssize_t count = read(fd, buffer, sizeof(buffer));
    if (count > 0) {
        printf("读取进程收到: %s\n", buffer);
    }
    
    close(fd);
}

3. 共享内存(Shared Memory)

特点

  • 最快的IPC方式
  • 多个进程可以直接读写同一块内存区域
  • 需要同步机制(如信号量)来避免竞态条件

写入进程 (shm_writer.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define SHM_SIZE 1024
#define SHM_KEY 0x1234
#define SEM_KEY 0x5678

// 信号量操作
void sem_wait(int semid) {
    struct sembuf sb = {0, -1, 0};
    semop(semid, &sb, 1);
}

void sem_signal(int semid) {
    struct sembuf sb = {0, 1, 0};
    semop(semid, &sb, 1);
}

void shared_memory_writer() {
    int shmid, semid;
    char *shared_memory;
    
    // 创建共享内存段
    shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget");
        return;
    }
    
    // 创建信号量
    semid = semget(SEM_KEY, 1, IPC_CREAT | 0666);
    if (semid == -1) {
        perror("semget");
        return;
    }
    
    // 初始化信号量为1(二进制信号量)
    semctl(semid, 0, SETVAL, 1);
    
    // 附加共享内存到当前进程
    shared_memory = shmat(shmid, NULL, 0);
    if (shared_memory == (char *)-1) {
        perror("shmat");
        return;
    }
    
    printf("写入进程: 准备写入数据...\n");
    
    // 使用信号量保护共享内存
    sem_wait(semid);
    
    // 写入数据到共享内存
    const char *message = "Hello from shared memory writer!";
    strcpy(shared_memory, message);
    printf("写入进程: 数据已写入共享内存\n");
    
    sem_signal(semid);
    
    // 等待用户输入,让读取进程有时间读取
    printf("按Enter键继续...\n");
    getchar();
    
    // 清理
    shmdt(shared_memory);
    shmctl(shmid, IPC_RMID, NULL);
    semctl(semid, 0, IPC_RMID);
}

读取进程 (shm_reader.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define SHM_SIZE 1024
#define SHM_KEY 0x1234
#define SEM_KEY 0x5678

void sem_wait(int semid) {
    struct sembuf sb = {0, -1, 0};
    semop(semid, &sb, 1);
}

void sem_signal(int semid) {
    struct sembuf sb = {0, 1, 0};
    semop(semid, &sb, 1);
}

void shared_memory_reader() {
    int shmid, semid;
    char *shared_memory;
    char buffer[SHM_SIZE];
    
    // 获取共享内存段
    shmid = shmget(SHM_KEY, SHM_SIZE, 0666);
    if (shmid == -1) {
        perror("shmget");
        return;
    }
    
    // 获取信号量
    semid = semget(SEM_KEY, 1, 0666);
    if (semid == -1) {
        perror("semget");
        return;
    }
    
    // 附加共享内存到当前进程
    shared_memory = shmat(shmid, NULL, 0);
    if (shared_memory == (char *)-1) {
        perror("shmat");
        return;
    }
    
    printf("读取进程: 等待数据...\n");
    
    // 使用信号量保护共享内存访问
    sem_wait(semid);
    
    // 从共享内存读取数据
    strcpy(buffer, shared_memory);
    printf("读取进程收到: %s\n", buffer);
    
    sem_signal(semid);
    
    // 分离共享内存
    shmdt(shared_memory);
}

4. 消息队列(Message Queue)

特点

  • 消息的链表,存储在内核中
  • 支持不同消息类型
  • 可以按类型读取消息

发送进程 (msg_sender.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <sys/ipc.h>

#define MSG_KEY 0x1234

// 消息结构
struct message {
    long mtype;
    char mtext[100];
    int mdata;
};

void message_queue_sender() {
    int msgid;
    struct message msg;
    
    // 创建消息队列
    msgid = msgget(MSG_KEY, IPC_CREAT | 0666);
    if (msgid == -1) {
        perror("msgget");
        return;
    }
    
    printf("发送进程: 准备发送消息...\n");
    
    // 发送类型1的消息
    msg.mtype = 1;
    strcpy(msg.mtext, "紧急消息");
    msg.mdata = 100;
    
    if (msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0) == -1) {
        perror("msgsnd");
        return;
    }
    printf("发送进程: 类型1消息已发送\n");
    
    // 发送类型2的消息
    msg.mtype = 2;
    strcpy(msg.mtext, "普通消息");
    msg.mdata = 200;
    
    if (msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0) == -1) {
        perror("msgsnd");
        return;
    }
    printf("发送进程: 类型2消息已发送\n");
    
    // 等待,让接收进程有时间接收
    sleep(2);
    
    // 删除消息队列
    msgctl(msgid, IPC_RMID, NULL);
    printf("发送进程: 消息队列已删除\n");
}

接收进程 (msg_receiver.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <sys/ipc.h>

#define MSG_KEY 0x1234

struct message {
    long mtype;
    char mtext[100];
    int mdata;
};

void message_queue_receiver() {
    int msgid;
    struct message msg;
    
    // 获取消息队列
    msgid = msgget(MSG_KEY, 0666);
    if (msgid == -1) {
        perror("msgget");
        return;
    }
    
    printf("接收进程: 等待消息...\n");
    
    // 接收类型1的消息(紧急消息)
    if (msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), 1, 0) == -1) {
        perror("msgrcv");
        return;
    }
    printf("接收进程收到类型1: %s, 数据: %d\n", msg.mtext, msg.mdata);
    
    // 接收类型2的消息(普通消息)
    if (msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), 2, 0) == -1) {
        perror("msgrcv");
        return;
    }
    printf("接收进程收到类型2: %s, 数据: %d\n", msg.mtext, msg.mdata);
    
    // 尝试非阻塞接收(应该没有消息了)
    if (msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), 0, IPC_NOWAIT) == -1) {
        printf("接收进程: 没有更多消息\n");
    }
}

5. 信号量(Semaphore)

特点

  • 用于进程同步,而不是数据传输
  • 控制对共享资源的访问
  • 可以用于解决竞态条件

生产者-消费者示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/ipc.h>

#define KEY 0x1234
#define BUFFER_SIZE 5

// 共享缓冲区结构
struct shared_buffer {
    int data[BUFFER_SIZE];
    int in;
    int out;
};

// 信号量操作
void P(int semid, int semnum) {
    struct sembuf sb = {semnum, -1, 0};
    semop(semid, &sb, 1);
}

void V(int semid, int semnum) {
    struct sembuf sb = {semnum, 1, 0};
    semop(semid, &sb, 1);
}

void producer() {
    int shmid, semid;
    struct shared_buffer *buffer;
    
    // 获取共享内存和信号量
    shmid = shmget(KEY, sizeof(struct shared_buffer), 0666);
    semid = semget(KEY, 2, 0666);
    
    if (shmid == -1 || semid == -1) {
        printf("请先运行初始化程序\n");
        return;
    }
    
    // 附加共享内存
    buffer = shmat(shmid, NULL, 0);
    
    for (int i = 0; i < 10; i++) {
        // 生产一个项目
        int item = i + 100;
        
        // 等待空位(empty信号量)
        P(semid, 0);
        
        // 进入临界区(mutex信号量)
        P(semid, 1);
        
        // 生产项目
        buffer->data[buffer->in] = item;
        printf("生产者: 生产项目 %d 到位置 %d\n", item, buffer->in);
        buffer->in = (buffer->in + 1) % BUFFER_SIZE;
        
        // 离开临界区
        V(semid, 1);
        
        // 通知有可用项目(full信号量)
        V(semid, 2);
        
        sleep(1);  // 模拟生产时间
    }
    
    shmdt(buffer);
}

void consumer() {
    int shmid, semid;
    struct shared_buffer *buffer;
    
    // 获取共享内存和信号量
    shmid = shmget(KEY, sizeof(struct shared_buffer), 0666);
    semid = semget(KEY, 2, 0666);
    
    if (shmid == -1 || semid == -1) {
        printf("请先运行初始化程序\n");
        return;
    }
    
    // 附加共享内存
    buffer = shmat(shmid, NULL, 0);
    
    for (int i = 0; i < 10; i++) {
        // 等待可用项目(full信号量)
        P(semid, 2);
        
        // 进入临界区(mutex信号量)
        P(semid, 1);
        
        // 消费项目
        int item = buffer->data[buffer->out];
        printf("消费者: 消费项目 %d 从位置 %d\n", item, buffer->out);
        buffer->out = (buffer->out + 1) % BUFFER_SIZE;
        
        // 离开临界区
        V(semid, 1);
        
        // 通知有空位(empty信号量)
        V(semid, 0);
        
        sleep(2);  // 模拟消费时间
    }
    
    shmdt(buffer);
}

// 初始化共享内存和信号量
void init_resources() {
    int shmid, semid;
    struct shared_buffer *buffer;
    
    // 创建共享内存
    shmid = shmget(KEY, sizeof(struct shared_buffer), IPC_CREAT | 0666);
    
    // 创建信号量集(3个信号量:empty, mutex, full)
    semid = semget(KEY, 3, IPC_CREAT | 0666);
    
    // 初始化信号量
    semctl(semid, 0, SETVAL, BUFFER_SIZE);  // empty = BUFFER_SIZE
    semctl(semid, 1, SETVAL, 1);            // mutex = 1
    semctl(semid, 2, SETVAL, 0);            // full = 0
    
    // 初始化共享缓冲区
    buffer = shmat(shmid, NULL, 0);
    buffer->in = 0;
    buffer->out = 0;
    shmdt(buffer);
    
    printf("资源初始化完成\n");
}

// 清理资源
void cleanup_resources() {
    int shmid = shmget(KEY, 0, 0);
    int semid = semget(KEY, 0, 0);
    
    if (shmid != -1) shmctl(shmid, IPC_RMID, NULL);
    if (semid != -1) semctl(semid, 0, IPC_RMID);
    
    printf("资源清理完成\n");
}

IPC方式比较总结

IPC方式通信关系数据传输同步需求性能复杂度
无名管道亲缘进程字节流自动同步
有名管道任意进程字节流自动同步
共享内存任意进程内存直接访问需要显式同步最高
消息队列任意进程消息自动同步
信号量任意进程无数据传输同步原语

选择建议

  1. 父子进程通信:无名管道
  2. 无亲缘关系进程:有名管道、共享内存、消息队列
  3. 高性能需求:共享内存(配合信号量)
  4. 结构化消息:消息队列
  5. 纯同步需求:信号量

这些IPC机制可以单独使用,也可以组合使用以满足复杂的进程间通信需求。

进程间同步

进程同步是指协调多个进程的执行顺序,确保它们以可预测的方式访问共享资源,避免竞态条件和数据不一致的问题。在多进程环境中,由于进程的并发执行和资源共享,同步机制至关重要。

1. 信号量(Semaphore)

信号量是最常用的进程同步机制,是一个计数器,用于控制多个进程对共享资源的访问。

System V 信号量
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/wait.h>

// 联合体用于semctl
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

// P操作 - 等待信号量
void sem_wait(int semid, int semnum) {
    struct sembuf sb = {semnum, -1, 0};
    semop(semid, &sb, 1);
}

// V操作 - 释放信号量
void sem_signal(int semid, int semnum) {
    struct sembuf sb = {semnum, 1, 0};
    semop(semid, &sb, 1);
}

// 二进制信号量示例(互斥锁)
void binary_semaphore_example() {
    int semid;
    key_t key = ftok(".", 'S');
    union semun arg;
    
    // 创建信号量集(1个信号量)
    semid = semget(key, 1, IPC_CREAT | 0666);
    
    // 初始化信号量为1(二进制信号量)
    arg.val = 1;
    semctl(semid, 0, SETVAL, arg);
    
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        printf("子进程尝试获取锁...\n");
        sem_wait(semid, 0);  // P操作
        
        printf("子进程获得锁,进入临界区\n");
        sleep(2);  // 模拟临界区操作
        printf("子进程离开临界区\n");
        
        sem_signal(semid, 0);  // V操作
        exit(0);
    } else {
        // 父进程
        sleep(1);  // 确保子进程先尝试获取锁
        printf("父进程尝试获取锁...\n");
        sem_wait(semid, 0);  // P操作(会阻塞直到子进程释放)
        
        printf("父进程获得锁,进入临界区\n");
        sleep(1);
        printf("父进程离开临界区\n");
        
        sem_signal(semid, 0);  // V操作
        
        wait(NULL);
        
        // 清理信号量
        semctl(semid, 0, IPC_RMID);
    }
}

// 计数信号量示例(控制资源池)
void counting_semaphore_example() {
    int semid;
    key_t key = ftok(".", 'C');
    union semun arg;
    
    // 创建3个资源的信号量
    semid = semget(key, 1, IPC_CREAT | 0666);
    arg.val = 3;  // 初始有3个可用资源
    semctl(semid, 0, SETVAL, arg);
    
    // 创建多个子进程竞争资源
    for (int i = 0; i < 5; i++) {
        if (fork() == 0) {
            printf("进程 %d 等待资源...\n", getpid());
            sem_wait(semid, 0);  // 获取资源
            
            printf("进程 %d 获得资源,使用中...\n", getpid());
            sleep(2);  // 使用资源
            
            sem_signal(semid, 0);  // 释放资源
            printf("进程 %d 释放资源\n", getpid());
            exit(0);
        }
    }
    
    // 等待所有子进程结束
    for (int i = 0; i < 5; i++) {
        wait(NULL);
    }
    
    semctl(semid, 0, IPC_RMID);
}
POSIX 命名信号量
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <sys/wait.h>

#define SEM_NAME "/my_named_sem"

void posix_semaphore_example() {
    sem_t *sem;
    
    // 创建命名信号量
    sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
    if (sem == SEM_FAILED) {
        perror("sem_open");
        return;
    }
    
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        printf("子进程等待信号量...\n");
        sem_wait(sem);
        
        printf("子进程进入临界区\n");
        sleep(2);
        printf("子进程离开临界区\n");
        
        sem_post(sem);
        sem_close(sem);
        exit(0);
    } else {
        // 父进程
        sleep(1);
        printf("父进程等待信号量...\n");
        sem_wait(sem);
        
        printf("父进程进入临界区\n");
        sleep(1);
        printf("父进程离开临界区\n");
        
        sem_post(sem);
        wait(NULL);
        
        sem_close(sem);
        sem_unlink(SEM_NAME);  // 删除命名信号量
    }
}

2. 文件锁(File Locking)

使用文件锁可以实现进程间的互斥访问。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <fcntl.h>
#include <sys/wait.h>

void file_lock_example() {
    const char *lockfile = "/tmp/my_lockfile";
    int fd;
    
    // 创建锁文件
    fd = open(lockfile, O_CREAT | O_RDWR, 0666);
    if (fd == -1) {
        perror("open");
        return;
    }
    
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        printf("子进程尝试获取文件锁...\n");
        
        // 获取排他锁(会阻塞直到获取)
        if (flock(fd, LOCK_EX) == -1) {
            perror("flock");
            exit(1);
        }
        
        printf("子进程获得文件锁\n");
        sleep(2);
        printf("子进程释放文件锁\n");
        
        flock(fd, LOCK_UN);  // 释放锁
        close(fd);
        exit(0);
    } else {
        // 父进程
        sleep(1);
        printf("父进程尝试获取文件锁...\n");
        
        // 非阻塞尝试获取锁
        if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
            printf("父进程: 锁被占用,等待...\n");
            
            // 阻塞等待
            flock(fd, LOCK_EX);
        }
        
        printf("父进程获得文件锁\n");
        sleep(1);
        printf("父进程释放文件锁\n");
        
        flock(fd, LOCK_UN);
        close(fd);
        wait(NULL);
        
        // 删除锁文件
        unlink(lockfile);
    }
}

// 使用fcntl记录锁
void fcntl_lock_example() {
    const char *filename = "/tmp/shared_file";
    int fd = open(filename, O_CREAT | O_RDWR, 0666);
    
    struct flock lock;
    
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程设置写锁
        lock.l_type = F_WRLCK;
        lock.l_whence = SEEK_SET;
        lock.l_start = 0;
        lock.l_len = 0;  // 锁定整个文件
        
        printf("子进程设置写锁...\n");
        fcntl(fd, F_SETLKW, &lock);  // 阻塞直到获取锁
        printf("子进程获得写锁\n");
        
        sleep(2);
        
        // 释放锁
        lock.l_type = F_UNLCK;
        fcntl(fd, F_SETLK, &lock);
        printf("子进程释放锁\n");
        
        close(fd);
        exit(0);
    } else {
        sleep(1);
        
        // 父进程尝试获取读锁
        lock.l_type = F_RDLCK;
        lock.l_whence = SEEK_SET;
        lock.l_start = 0;
        lock.l_len = 0;
        
        printf("父进程尝试获取读锁...\n");
        if (fcntl(fd, F_SETLK, &lock) == -1) {
            printf("父进程: 文件被锁定,无法获取读锁\n");
        } else {
            printf("父进程获得读锁\n");
            lock.l_type = F_UNLCK;
            fcntl(fd, F_SETLK, &lock);
        }
        
        wait(NULL);
        close(fd);
        unlink(filename);
    }
}

3. 互斥锁在共享内存中

虽然互斥锁通常用于线程,但可以通过共享内存用于进程间同步。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <pthread.h>

typedef struct {
    pthread_mutex_t mutex;
    pthread_mutexattr_t mutex_attr;
    int shared_counter;
} shared_data_t;

void shared_memory_mutex_example() {
    // 创建共享内存区域
    shared_data_t *shared = mmap(NULL, sizeof(shared_data_t),
                                PROT_READ | PROT_WRITE,
                                MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (shared == MAP_FAILED) {
        perror("mmap");
        return;
    }
    
    // 初始化互斥锁属性为进程共享
    pthread_mutexattr_init(&shared->mutex_attr);
    pthread_mutexattr_setpshared(&shared->mutex_attr, PTHREAD_PROCESS_SHARED);
    
    // 初始化互斥锁
    pthread_mutex_init(&shared->mutex, &shared->mutex_attr);
    
    shared->shared_counter = 0;
    
    // 创建多个子进程
    for (int i = 0; i < 3; i++) {
        if (fork() == 0) {
            // 子进程
            for (int j = 0; j < 5; j++) {
                pthread_mutex_lock(&shared->mutex);
                
                // 临界区开始
                int temp = shared->shared_counter;
                usleep(100000);  // 模拟处理时间
                shared->shared_counter = temp + 1;
                printf("进程 %d: 计数器 = %d\n", getpid(), shared->shared_counter);
                // 临界区结束
                
                pthread_mutex_unlock(&shared->mutex);
                usleep(50000);
            }
            exit(0);
        }
    }
    
    // 等待所有子进程
    for (int i = 0; i < 3; i++) {
        wait(NULL);
    }
    
    printf("最终计数器值: %d (应该是15)\n", shared->shared_counter);
    
    // 清理
    pthread_mutex_destroy(&shared->mutex);
    pthread_mutexattr_destroy(&shared->mutex_attr);
    munmap(shared, sizeof(shared_data_t));
}

4. 条件变量在共享内存中

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <pthread.h>

typedef struct {
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    pthread_mutexattr_t mutex_attr;
    pthread_condattr_t cond_attr;
    int data_ready;
    int shared_data;
} shared_cond_t;

void condition_variable_example() {
    shared_cond_t *shared = mmap(NULL, sizeof(shared_cond_t),
                                PROT_READ | PROT_WRITE,
                                MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    
    // 初始化属性
    pthread_mutexattr_init(&shared->mutex_attr);
    pthread_mutexattr_setpshared(&shared->mutex_attr, PTHREAD_PROCESS_SHARED);
    pthread_condattr_init(&shared->cond_attr);
    pthread_condattr_setpshared(&shared->cond_attr, PTHREAD_PROCESS_SHARED);
    
    // 初始化互斥锁和条件变量
    pthread_mutex_init(&shared->mutex, &shared->mutex_attr);
    pthread_cond_init(&shared->cond, &shared->cond_attr);
    
    shared->data_ready = 0;
    shared->shared_data = 0;
    
    pid_t producer = fork();
    if (producer == 0) {
        // 生产者进程
        for (int i = 1; i <= 3; i++) {
            pthread_mutex_lock(&shared->mutex);
            
            // 生产数据
            shared->shared_data = i * 100;
            shared->data_ready = 1;
            printf("生产者: 生产数据 %d\n", shared->shared_data);
            
            // 通知消费者
            pthread_cond_signal(&shared->cond);
            pthread_mutex_unlock(&shared->mutex);
            
            sleep(1);  // 生产间隔
        }
        exit(0);
    }
    
    pid_t consumer = fork();
    if (consumer == 0) {
        // 消费者进程
        for (int i = 0; i < 3; i++) {
            pthread_mutex_lock(&shared->mutex);
            
            // 等待数据就绪
            while (!shared->data_ready) {
                printf("消费者: 等待数据...\n");
                pthread_cond_wait(&shared->cond, &shared->mutex);
            }
            
            // 消费数据
            printf("消费者: 消费数据 %d\n", shared->shared_data);
            shared->data_ready = 0;
            
            pthread_mutex_unlock(&shared->mutex);
        }
        exit(0);
    }
    
    // 等待子进程结束
    wait(NULL);
    wait(NULL);
    
    // 清理
    pthread_mutex_destroy(&shared->mutex);
    pthread_cond_destroy(&shared->cond);
    pthread_mutexattr_destroy(&shared->mutex_attr);
    pthread_condattr_destroy(&shared->cond_attr);
    munmap(shared, sizeof(shared_cond_t));
}

5. 屏障(Barrier)

屏障用于同步多个进程,使它们在某个点等待,直到所有进程都到达后才继续执行。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <pthread.h>

#define NUM_PROCESSES 4

typedef struct {
    pthread_barrier_t barrier;
    pthread_barrierattr_t barrier_attr;
} shared_barrier_t;

void barrier_example() {
    shared_barrier_t *shared = mmap(NULL, sizeof(shared_barrier_t),
                                   PROT_READ | PROT_WRITE,
                                   MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    
    // 初始化屏障属性
    pthread_barrierattr_init(&shared->barrier_attr);
    pthread_barrierattr_setpshared(&shared->barrier_attr, PTHREAD_PROCESS_SHARED);
    
    // 初始化屏障,等待4个进程
    pthread_barrier_init(&shared->barrier, &shared->barrier_attr, NUM_PROCESSES);
    
    printf("创建 %d 个进程在屏障处同步\n", NUM_PROCESSES);
    
    // 创建子进程
    for (int i = 0; i < NUM_PROCESSES - 1; i++) {
        if (fork() == 0) {
            // 子进程工作
            printf("进程 %d 开始第一阶段工作\n", getpid());
            sleep(i + 1);  // 不同的工作负载
            
            printf("进程 %d 到达屏障点,等待其他进程...\n", getpid());
            pthread_barrier_wait(&shared->barrier);
            
            printf("进程 %d 通过屏障,开始第二阶段工作\n", getpid());
            sleep(1);
            
            printf("进程 %d 完成\n", getpid());
            exit(0);
        }
    }
    
    // 父进程也参与
    printf("父进程 %d 开始第一阶段工作\n", getpid());
    sleep(2);
    
    printf("父进程 %d 到达屏障点,等待其他进程...\n", getpid());
    pthread_barrier_wait(&shared->barrier);
    
    printf("父进程 %d 通过屏障,开始第二阶段工作\n", getpid());
    sleep(1);
    printf("父进程 %d 完成\n", getpid());
    
    // 等待所有子进程
    for (int i = 0; i < NUM_PROCESSES - 1; i++) {
        wait(NULL);
    }
    
    // 清理
    pthread_barrier_destroy(&shared->barrier);
    pthread_barrierattr_destroy(&shared->barrier_attr);
    munmap(shared, sizeof(shared_barrier_t));
}

6. 读写锁在共享内存中

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <pthread.h>

typedef struct {
    pthread_rwlock_t rwlock;
    pthread_rwlockattr_t rwlock_attr;
    int shared_data;
    int reader_count;
} shared_rwlock_t;

void reader_writer_example() {
    shared_rwlock_t *shared = mmap(NULL, sizeof(shared_rwlock_t),
                                  PROT_READ | PROT_WRITE,
                                  MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    
    // 初始化读写锁属性
    pthread_rwlockattr_init(&shared->rwlock_attr);
    pthread_rwlockattr_setpshared(&shared->rwlock_attr, PTHREAD_PROCESS_SHARED);
    
    // 初始化读写锁
    pthread_rwlock_init(&shared->rwlock, &shared->rwlock_attr);
    
    shared->shared_data = 0;
    shared->reader_count = 0;
    
    // 创建读者进程
    for (int i = 0; i < 3; i++) {
        if (fork() == 0) {
            // 读者进程
            for (int j = 0; j < 3; j++) {
                pthread_rwlock_rdlock(&shared->rwlock);
                
                shared->reader_count++;
                printf("读者 %d: 读取数据 %d (当前读者数: %d)\n", 
                       getpid(), shared->shared_data, shared->reader_count);
                sleep(1);
                shared->reader_count--;
                
                pthread_rwlock_unlock(&shared->rwlock);
                sleep(2);
            }
            exit(0);
        }
    }
    
    // 创建写者进程
    for (int i = 0; i < 2; i++) {
        if (fork() == 0) {
            // 写者进程
            for (int j = 0; j < 2; j++) {
                pthread_rwlock_wrlock(&shared->rwlock);
                
                printf("写者 %d: 开始写入...\n", getpid());
                shared->shared_data += 10;
                sleep(2);
                printf("写者 %d: 写入完成,新值: %d\n", getpid(), shared->shared_data);
                
                pthread_rwlock_unlock(&shared->rwlock);
                sleep(3);
            }
            exit(0);
        }
    }
    
    // 等待所有子进程
    for (int i = 0; i < 5; i++) {
        wait(NULL);
    }
    
    printf("最终数据值: %d\n", shared->shared_data);
    
    // 清理
    pthread_rwlock_destroy(&shared->rwlock);
    pthread_rwlockattr_destroy(&shared->rwlock_attr);
    munmap(shared, sizeof(shared_rwlock_t));
}

进程同步方法比较总结

同步方法适用场景优点缺点
信号量互斥访问、资源计数、生产者消费者灵活,支持计数需要手动管理
文件锁简单互斥、文件访问控制简单易用,不需要特殊IPC性能较低,基于文件系统
共享内存+互斥锁高性能共享数据访问性能高,类似线程同步复杂,需要共享内存
条件变量复杂的等待/通知场景支持复杂的同步模式必须与互斥锁配合使用
屏障并行计算阶段同步简单实现阶段同步需要预先知道进程数量
读写锁读多写少的场景提高读并发性能实现相对复杂

同步方法的选择指南

  1. 简单互斥:文件锁或二进制信号量
  2. 资源池管理:计数信号量
  3. 高性能数据共享:共享内存 + 互斥锁/读写锁
  4. 复杂协调:条件变量
  5. 并行计算:屏障
  6. 读多写少:读写锁

实践

  1. 避免死锁:按照固定顺序获取锁
  2. 减少锁粒度:只锁定必要的数据
  3. 超时机制:为锁操作设置超时
  4. 错误处理:检查所有同步原语的返回值
  5. 资源清理:进程退出前释放所有同步资源

正确的同步机制选择和使用对于构建稳定、高效的多进程应用至关重要。

现代进程使用模式

1. 进程池模式

// 预创建进程,避免频繁创建销毁
typedef struct {
    pid_t* workers;
    int worker_count;
    task_queue_t queue;
} process_pool_t;

void process_pool_init(process_pool_t* pool, int count) {
    pool->workers = malloc(count * sizeof(pid_t));
    pool->worker_count = count;
    
    for (int i = 0; i < count; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            // 工作进程
            worker_loop(pool);
            _exit(0);
        } else {
            pool->workers[i] = pid;
        }
    }
}

2. 监督树模式

// 父进程监督子进程,崩溃时重启
void supervisor() {
    while (1) {
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程执行实际工作
            worker_main();
            _exit(0);
        } else {
            // 父进程等待并处理崩溃
            int status;
            waitpid(pid, &status, 0);
            
            if (WIFEXITED(status) && WEXITSTATUS(status) == RESTART_CODE) {
                printf("子进程正常退出,准备重启...\n");
                sleep(1);  // 避免频繁重启
            } else {
                printf("子进程异常退出,重启中...\n");
                sleep(5);  // 异常时等待更久
            }
        }
    }
}

多进程性能优化

多进程编程的性能优化旨在减少进程开销、提高资源利用率、优化通信效率,从而提升整体系统性能。以下是从各个角度进行的详细优化策略。

1. 进程创建与销毁优化

进程池技术
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mman.h>

#define WORKER_COUNT 4
#define TASK_QUEUE_SIZE 100

typedef struct {
    int task_id;
    int data;
    int result;
} task_t;

typedef struct {
    task_t tasks[TASK_QUEUE_SIZE];
    int head;
    int tail;
    int count;
    pthread_mutex_t mutex;
    pthread_cond_t not_empty;
    pthread_cond_t not_full;
} task_queue_t;

typedef struct {
    pid_t workers[WORKER_COUNT];
    task_queue_t *queue;
    int running;
} process_pool_t;

// 初始化进程池
void process_pool_init(process_pool_t *pool) {
    // 创建共享任务队列
    pool->queue = mmap(NULL, sizeof(task_queue_t), 
                      PROT_READ | PROT_WRITE, 
                      MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    
    // 初始化队列
    pool->queue->head = 0;
    pool->queue->tail = 0;
    pool->queue->count = 0;
    
    // 初始化进程共享的锁和条件变量
    pthread_mutexattr_t mutex_attr;
    pthread_mutexattr_init(&mutex_attr);
    pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
    pthread_mutex_init(&pool->queue->mutex, &mutex_attr);
    
    pthread_condattr_t cond_attr;
    pthread_condattr_init(&cond_attr);
    pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);
    pthread_cond_init(&pool->queue->not_empty, &cond_attr);
    pthread_cond_init(&pool->queue->not_full, &cond_attr);
    
    pool->running = 1;
    
    // 创建工作进程
    for (int i = 0; i < WORKER_COUNT; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            worker_process(pool->queue);
            exit(0);
        } else {
            pool->workers[i] = pid;
        }
    }
}

// 工作进程主循环
void worker_process(task_queue_t *queue) {
    while (1) {
        pthread_mutex_lock(&queue->mutex);
        
        // 等待任务
        while (queue->count == 0) {
            pthread_cond_wait(&queue->not_empty, &queue->mutex);
        }
        
        // 获取任务
        task_t *task = &queue->tasks[queue->head];
        queue->head = (queue->head + 1) % TASK_QUEUE_SIZE;
        queue->count--;
        
        pthread_cond_signal(&queue->not_full);
        pthread_mutex_unlock(&queue->mutex);
        
        // 处理任务
        task->result = process_task(task->data);
        
        printf("Worker %d: Task %d completed, result=%d\n", 
               getpid(), task->task_id, task->result);
    }
}

// 提交任务
void submit_task(process_pool_t *pool, task_t *task) {
    pthread_mutex_lock(&pool->queue->mutex);
    
    // 等待队列有空位
    while (pool->queue->count == TASK_QUEUE_SIZE) {
        pthread_cond_wait(&pool->queue->not_full, &pool->queue->mutex);
    }
    
    // 添加任务
    pool->queue->tasks[pool->queue->tail] = *task;
    pool->queue->tail = (pool->queue->tail + 1) % TASK_QUEUE_SIZE;
    pool->queue->count++;
    
    pthread_cond_signal(&pool->queue->not_empty);
    pthread_mutex_unlock(&pool->queue->mutex);
}
预创建进程
// 预创建进程,避免运行时fork开销
void precreate_processes() {
    pid_t workers[10];
    
    // 预创建进程
    for (int i = 0; i < 10; i++) {
        workers[i] = fork();
        if (workers[i] == 0) {
            // 子进程进入等待状态
            pause();  // 等待信号唤醒
            exit(0);
        }
    }
    
    // 当需要时唤醒进程,而不是重新创建
    // kill(workers[0], SIGCONT);
}

2. 进程间通信优化

共享内存优化
#include <sys/shm.h>
#include <sys/mman.h>

// 大页面共享内存(如果系统支持)
void huge_page_shared_memory() {
    size_t size = 2 * 1024 * 1024;  // 2MB
    
    // 尝试使用大页面
    void *shm = mmap(NULL, size, PROT_READ | PROT_WRITE,
                     MAP_SHARED | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
    
    if (shm == MAP_FAILED) {
        // 回退到普通共享内存
        shm = mmap(NULL, size, PROT_READ | PROT_WRITE,
                   MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    }
    
    if (shm != MAP_FAILED) {
        printf("共享内存分配成功: %p\n", shm);
    }
}

// 内存对齐优化
typedef struct __attribute__((aligned(64))) {
    int data1;
    int data2;
    char padding[56];  // 填充到缓存行大小
} cache_aligned_data_t;

void cache_optimized_shared_memory() {
    // 确保每个进程访问的数据在不同的缓存行
    cache_aligned_data_t *shared_data = mmap(NULL, sizeof(cache_aligned_data_t) * WORKER_COUNT,
                                            PROT_READ | PROT_WRITE,
                                            MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    
    // 每个进程访问自己缓存行对齐的数据
    for (int i = 0; i < WORKER_COUNT; i++) {
        if (fork() == 0) {
            cache_aligned_data_t *my_data = &shared_data[i];
            // 处理数据,避免缓存行竞争
            my_data->data1 = i * 100;
            exit(0);
        }
    }
}
批量数据传输
// 批量处理减少IPC调用次数
typedef struct {
    int items[1000];
    int count;
} batch_data_t;

void batch_ipc_example() {
    int shmid = shmget(IPC_PRIVATE, sizeof(batch_data_t), IPC_CREAT | 0666);
    batch_data_t *batch = shmat(shmid, NULL, 0);
    
    if (fork() == 0) {
        // 生产者:批量生产数据
        for (int i = 0; i < 10; i++) {
            // 积累一批数据
            batch->count = 100;
            for (int j = 0; j < 100; j++) {
                batch->items[j] = i * 100 + j;
            }
            
            // 通知消费者(一次IPC调用传输100个数据项)
            // 使用信号量或条件变量通知
            sleep(1);  // 模拟生产时间
        }
        exit(0);
    } else {
        // 消费者:批量消费数据
        for (int i = 0; i < 10; i++) {
            // 等待批量数据就绪
            while (batch->count == 0) {
                usleep(1000);
            }
            
            // 处理整批数据
            printf("收到 %d 个数据项\n", batch->count);
            batch->count = 0;  // 重置计数
        }
        wait(NULL);
    }
}

3. CPU和内存优化

CPU亲和性设置
#define _GNU_SOURCE
#include <sched.h>

void set_cpu_affinity(pid_t pid, int cpu_id) {
    cpu_set_t set;
    CPU_ZERO(&set);
    CPU_SET(cpu_id, &set);
    
    if (sched_setaffinity(pid, sizeof(cpu_set_t), &set) == -1) {
        perror("sched_setaffinity");
    }
}

void optimized_cpu_affinity() {
    int num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
    
    for (int i = 0; i < WORKER_COUNT; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程绑定到特定CPU
            set_cpu_affinity(0, i % num_cpus);  // 循环分配CPU
            
            // 工作代码
            while (1) {
                // 计算密集型任务
            }
            exit(0);
        }
    }
}

// NUMA感知的内存分配
void numa_aware_allocation() {
    // 在支持NUMA的系统上,将进程和其使用的内存绑定到同一NUMA节点
    // 这通常需要numactl库或系统调用
}

内存布局优化

// 避免虚假共享
typedef struct {
    int counter __attribute__((aligned(64)));  // 缓存行对齐
    char padding[60];  // 填充到64字节
} aligned_counter_t;

void avoid_false_sharing() {
    aligned_counter_t *counters = mmap(NULL, sizeof(aligned_counter_t) * WORKER_COUNT,
                                      PROT_READ | PROT_WRITE,
                                      MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    
    // 每个进程更新自己的计数器,不会互相干扰缓存行
    for (int i = 0; i < WORKER_COUNT; i++) {
        if (fork() == 0) {
            for (int j = 0; j < 1000000; j++) {
                counters[i].counter++;  // 独立的缓存行
            }
            exit(0);
        }
    }
}

4. I/O优化

异步I/O和I/O多路复用
#include <aio.h>
#include <fcntl.h>

void async_io_example() {
    int fd = open("large_file.dat", O_RDONLY);
    char buffer[4096];
    
    struct aiocb aio_request = {0};
    aio_request.aio_fildes = fd;
    aio_request.aio_buf = buffer;
    aio_request.aio_nbytes = sizeof(buffer);
    aio_request.aio_offset = 0;
    
    // 发起异步读取
    if (aio_read(&aio_request) == -1) {
        perror("aio_read");
        return;
    }
    
    // 进程可以继续做其他工作
    printf("异步I/O进行中,继续处理其他任务...\n");
    
    // 等待I/O完成
    while (aio_error(&aio_request) == EINPROGRESS) {
        usleep(1000);  // 做其他工作
    }
    
    // I/O完成
    ssize_t bytes_read = aio_return(&aio_request);
    printf("读取了 %zd 字节数据\n", bytes_read);
    close(fd);
}

// 使用epoll管理多个I/O
void epoll_multiplexing() {
    int epoll_fd = epoll_create1(0);
    struct epoll_event event;
    
    // 添加多个文件描述符到epoll
    // ... 设置代码
    
    while (1) {
        struct epoll_event events[10];
        int n = epoll_wait(epoll_fd, events, 10, -1);
        
        for (int i = 0; i < n; i++) {
            if (events[i].events & EPOLLIN) {
                // 处理可读事件
                handle_readable(events[i].data.fd);
            }
        }
    }
}

5. 负载均衡优化

工作窃取(Work Stealing)
typedef struct {
    task_t *local_queue;
    int local_head;
    int local_tail;
    task_t *global_queues;
    int num_workers;
} work_stealing_pool_t;

void work_stealing_worker(work_stealing_pool_t *pool, int worker_id) {
    while (1) {
        // 首先从自己的本地队列获取任务
        task_t *task = get_local_task(pool, worker_id);
        
        if (task == NULL) {
            // 本地队列为空,尝试从其他工作者窃取任务
            for (int i = 0; i < pool->num_workers; i++) {
                if (i != worker_id) {
                    task = steal_task(pool, i);
                    if (task != NULL) break;
                }
            }
        }
        
        if (task != NULL) {
            process_task(task);
        } else {
            // 没有任务,休眠一会儿
            usleep(1000);
        }
    }
}
动态负载均衡
typedef struct {
    int *worker_loads;
    int num_workers;
    pthread_mutex_t load_mutex;
} load_balancer_t;

int get_least_loaded_worker(load_balancer_t *balancer) {
    int min_load = INT_MAX;
    int best_worker = 0;
    
    pthread_mutex_lock(&balancer->load_mutex);
    for (int i = 0; i < balancer->num_workers; i++) {
        if (balancer->worker_loads[i] < min_load) {
            min_load = balancer->worker_loads[i];
            best_worker = i;
        }
    }
    balancer->worker_loads[best_worker]++;
    pthread_mutex_unlock(&balancer->load_mutex);
    
    return best_worker;
}

void update_worker_load(load_balancer_t *balancer, int worker_id, int load_change) {
    pthread_mutex_lock(&balancer->load_mutex);
    balancer->worker_loads[worker_id] += load_change;
    pthread_mutex_unlock(&balancer->load_mutex);
}

6. 同步优化

无锁数据结构
#include <stdatomic.h>

// 无锁队列
typedef struct {
    _Atomic int head;
    _Atomic int tail;
    task_t tasks[TASK_QUEUE_SIZE];
} lockfree_queue_t;

int lockfree_enqueue(lockfree_queue_t *queue, task_t task) {
    int current_tail = atomic_load(&queue->tail);
    int next_tail = (current_tail + 1) % TASK_QUEUE_SIZE;
    
    if (next_tail == atomic_load(&queue->head)) {
        return -1;  // 队列满
    }
    
    queue->tasks[current_tail] = task;
    atomic_store(&queue->tail, next_tail);
    return 0;
}

int lockfree_dequeue(lockfree_queue_t *queue, task_t *task) {
    int current_head = atomic_load(&queue->head);
    if (current_head == atomic_load(&queue->tail)) {
        return -1;  // 队列空
    }
    
    *task = queue->tasks[current_head];
    atomic_store(&queue->head, (current_head + 1) % TASK_QUEUE_SIZE);
    return 0;
}
读写锁优化
// 读多写少的场景使用读写锁
void read_heavy_workload() {
    pthread_rwlock_t rwlock;
    pthread_rwlock_init(&rwlock, NULL);
    
    // 多个读者可以并发
    if (fork() == 0) {
        pthread_rwlock_rdlock(&rwlock);
        // 读取数据
        pthread_rwlock_unlock(&rwlock);
        exit(0);
    }
    
    // 写者需要独占访问
    if (fork() == 0) {
        pthread_rwlock_wrlock(&rwlock);
        // 修改数据
        pthread_rwlock_unlock(&rwlock);
        exit(0);
    }
}

7. 监控和调优

性能监控
#include <sys/resource.h>

void monitor_performance() {
    struct rusage usage;
    
    while (1) {
        getrusage(RUSAGE_SELF, &usage);
        
        printf("用户CPU时间: %ld.%06ld秒\n", 
               usage.ru_utime.tv_sec, usage.ru_utime.tv_usec);
        printf("系统CPU时间: %ld.%06ld秒\n", 
               usage.ru_stime.tv_sec, usage.ru_stime.tv_usec);
        printf("页面错误: %ld\n", usage.ru_majflt);
        printf("自愿上下文切换: %ld\n", usage.ru_nvcsw);
        printf("非自愿上下文切换: %ld\n", usage.ru_nivcsw);
        
        sleep(5);  // 每5秒监控一次
    }
}

// 使用perf_event进行详细性能分析
void perf_monitoring() {
    // 需要Linux perf_event支持
    // 可以监控缓存命中率、分支预测失败、指令周期等硬件计数器
}

8. 编译优化

编译选项优化
# 使用优化的编译选项
gcc -O3 -march=native -mtune=native -flto -pthread program.c -o program

# 针对特定微架构优化
gcc -O3 -march=skylake -mtune=skylake program.c -o program
链接时优化(LTO)
// 在多个源文件中使用LTO
// 编译时:gcc -O3 -flto -c file1.c
//          gcc -O3 -flto -c file2.c  
// 链接时:gcc -O3 -flto file1.o file2.o -o program

性能优化总结

优化领域具体技术预期收益复杂度
进程管理进程池、预创建减少90%创建开销中等
通信优化批量传输、共享内存减少50-80%通信开销
CPU优化CPU亲和性、NUMA感知提高20-40%性能中等
内存优化缓存对齐、大页面减少缓存失效,提高内存带宽中等
I/O优化异步I/O、I/O多路复用提高I/O密集型应用性能
负载均衡工作窃取、动态调度提高资源利用率10-30%
同步优化无锁数据结构、读写锁减少锁竞争,提高并发性
监控调优性能分析、硬件计数器识别瓶颈,针对性优化中等

实践建议

  1. 先测量后优化:使用性能分析工具找到真正的瓶颈
  2. 渐进式优化:一次优化一个方面,验证效果
  3. 考虑可维护性:不要为了性能牺牲代码可读性和可维护性
  4. 测试不同负载:确保优化在各种工作负载下都有效
  5. 文档化优化:记录为什么选择某种优化策略
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值