Linux 多线程 / 多进程编程、同步互斥、信号机制完整梳理

📅 2026/7/18 17:27:24 👁️ 阅读次数 📝 编程学习
Linux 多线程 / 多进程编程、同步互斥、信号机制完整梳理

一、多进程编程(进程:独立地址空间,资源隔离)

1. 创建进程核心 API

  1. fork()
    • 调用一次返回两次:父进程返回子进程 PID,子进程返回 0;失败返回 - 1。
    • 写时复制 COW:初期父子共享页表,修改内存才拷贝,开销大。
    • 示例逻辑:
    pid_t pid = fork(); if(pid < 0) { perror("fork"); } else if(pid == 0) { /* 子进程 */ } else { /* 父进程,pid是子进程号 */ }
  2. vfork()不拷贝地址空间,子进程直接复用父进程内存,必须立刻exec/_exit,现已基本废弃。
  3. exec 系列(替换进程镜像)execl/execlp/execle/execv/execvp/execve,fork 后调用执行新程序。
  4. 进程回收 wait /waitpid
    • wait():阻塞等待任意子进程退出,获取退出状态,处理僵尸进程。
    • waitpid(pid, &status, WNOHANG):非阻塞,可指定等待某一子进程。
    • 僵尸进程:子进程先退出、父进程未回收,PCB 残留;孤儿进程:父进程先退出,子进程由init/systemd收养。

2. 进程间通信 IPC(多进程数据交换)

  1. 管道 pipe:单向,仅父子进程通信
  2. 命名管道 FIFO:无亲缘进程可用
  3. 共享内存 shmget/shmat/shmdt/shmctl:最快 IPC,需手动同步
  4. 消息队列 msgget/msgsnd/msgrcv:带类型消息缓冲
  5. 信号量 semget/semop/semctl:专门用于进程同步互斥
  6. Socket:本地域 socket,跨进程 / 跨机器通用

二、多线程编程(线程:进程内执行流,共享地址空间)

1. pthread 核心 API

  • 创建:pthread_create(&tid, attr, start_routine, arg)
  • 等待回收:pthread_join(tid, &retval)(阻塞回收)
  • 分离线程:pthread_detach(tid),线程退出自动释放资源,无需 join
  • 线程退出:pthread_exit(void *ret);主线程exit()会杀死所有子线程,pthread_exit()仅结束主线程、子线程继续运行
  • 获取自身 tid:pthread_self()

2. 线程独有 / 共享资源

  • 共享:全局变量、堆、文件描述符、信号处理函数、代码段
  • 独有:栈、寄存器、errno、线程私有数据 TSD、调度优先级

三、同步与互斥(解决竞态条件:多执行流并发修改共享资源)

(一)互斥:同一时刻仅一个执行流访问临界区

1. 互斥锁 pthread_mutex_t(线程专用)
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&mtx); // 加锁,阻塞 pthread_mutex_unlock(&mtx); // 解锁 pthread_mutex_trylock(&mtx);// 非阻塞加锁,失败返回EBUSY

类型:普通锁、递归锁(同一线程可多次 lock)、错误检测锁。

2. 进程间互斥:信号量 semaphore
  • System V 信号量:semget/semop/semctl,支持多计数器,常用于多进程互斥同步
  • POSIX 无名信号量:存于共享内存,多进程 / 线程可用
  • POSIX 有名信号量:sem_open,磁盘持久化,无亲缘进程通信

(二)同步:控制执行流先后顺序

  1. 条件变量 pthread_cond_t(配合互斥锁使用)

    • pthread_cond_wait(cond, mtx):释放锁并阻塞,被唤醒后重新加锁
    • pthread_cond_signal:唤醒一个等待线程
    • pthread_cond_broadcast:唤醒全部等待线程典型场景:生产者消费者模型。
  2. 读写锁 pthread_rwlock_t

    • 读共享、写独占:大量读少量写场景提升并发
    • rdlock多线程同时持有,wrlock独占。
  3. 自旋锁 pthread_spinlock_t忙等循环,不切换上下文;适合临界区极短、CPU 充足场景。

(三)进程同步补充

  1. 文件锁 fcntl:文件级互斥,简单轻量
  2. 共享内存 + 信号量:高性能多进程并发读写

四、Linux 信号处理机制

1. 信号基础

信号是内核 / 进程发给另一进程的异步通知,中断程序正常执行:

  • 常见信号:
    • SIGINT (2) Ctrl+C;SIGKILL (9) 强制杀死,不可捕获;SIGSEGV (11) 段错误;SIGCHLD 子进程退出;SIGALRM 定时器信号
  • 信号状态:递达、未决、阻塞(信号屏蔽字 mask)

2. 信号注册 API

  1. signal(int sig, void (*handler)(int)):简易接口,跨平台兼容性差
  2. sigaction(int sig, struct sigaction *act, oldact):推荐,精细控制
    • sa_handler:普通信号处理函数
    • sa_sigaction:带附加信息的处理函数(支持 siginfo_t)
    • sa_mask:执行 handler 时临时屏蔽的信号集
    • sa_flags:SA_RESTART(系统调用自动重启)、SA_NOCLDWAIT(SIGCHLD 不产生僵尸)

3. 信号集操作(屏蔽 / 解除屏蔽信号)

sigset_t set; sigemptyset(&set); // 清空集合 sigaddset(&set, SIGINT); // 添加信号 sigprocmask(SIG_BLOCK, &set, NULL); // 阻塞指定信号 sigprocmask(SIG_UNBLOCK, &set, NULL); // 解除阻塞

4. 信号等待

  • pause():阻塞直到收到任意信号
  • sigsuspend(&mask):临时替换屏蔽字并阻塞,原子操作,避免竞态

5. 多进程 / 多线程信号特性

  1. 进程信号:SIGCHLD 用于父进程异步回收子进程,避免轮询 waitpid
  2. 线程信号规则
    • 信号屏蔽字是线程私有,每个线程可独立屏蔽不同信号
    • 同步信号(SIGSEGV/SIGFPE)发送到触发线程;异步信号随机发给任意未屏蔽该信号的线程
    • 多线程程序推荐:单独创建一个专用线程统一处理所有异步信号,其余线程全部屏蔽信号,简化逻辑

6. 信号处理限制

信号 handler 内只能调用异步安全函数(write、sigaddset 等),禁止 malloc、printf、pthread 系列等非安全函数,易死锁 / 内存损坏。

五、面试高频总结(可直接用于简历 / 口述)

  1. 进程与线程区别:进程资源隔离、切换开销大;线程共享地址空间、切换轻量,线程依赖进程存在。
  2. 互斥 vs 同步:互斥保证同一时间唯一访问临界区;同步控制执行先后顺序,条件变量 + 互斥锁是标准组合。
  3. 锁选择:短临界区用自旋锁;普通线程互斥用 mutex;多读少写用读写锁;多进程同步用信号量。
  4. 信号关键点:SIGKILL 无法捕获;线程独立屏蔽字;handler 仅可调用异步安全函数;SIGCHLD 解决僵尸进程。
  5. 生产者消费者经典模型实现:线程版 (mutex+cond)、进程版 (共享内存 + 信号量)。