进程间通信:信号量
2026/9/19 5:47:31 网站建设 项目流程

信号量

1.1信号量的描述

信号量是一个特殊变量,一般取正数值,用于控制对共享资源的访问。

概念说明
信号量的值代表允许访问的资源数目
P操作(sem_wait获取资源:信号量值原子减1;若值为0,则阻塞等待
V操作(sem_post释放资源:信号量值原子加1;唤醒等待的线程/进程
二值信号量值只取0 或 1(相当于互斥锁)
计数信号量> 1(控制多个资源,如连接池)

信号量本质是一个"计数器"

  • >0→ 有可用资源,拿一个走(值减1)

  • ==0→ 没资源了,要排队等(阻塞)

  • 用完归还 → 值加1,唤醒排队的人

临界资源:同一时刻,只允许被一个进程或线程访问的资源(如共享变量、打印机)

临界区:访问临界资源的代码段(需要用信号量/锁保护)


1.2信号量使用

头文件sem.h

union semun{ int val; }; void sem_init(); // 初始化信号量 void sem_p(); // P操作(申请资源) void sem_v(); // V操作(释放资源) void sem_destory(); // 销毁信号量(拼写应为 destroy)

sem.c实现

信号量核心函数与结构体

1.semget—— 创建或获取信号量集
#include <sys/sem.h> int semget(key_t key, int nsems, int semflg);
参数说明
key全局唯一标识符(ftok生成,或IPC_PRIVATE
nsems信号量个数(通常为 1)
semflg权限标志(如IPC_CREAT | 0666
返回值说明
>= 0成功,返回信号量集 ID(semid)
-1失败
2.semctl—— 控制信号量集
int semctl(int semid, int semnum, int cmd, ...);
参数说明
semidsemget返回的信号量集 ID
semnum操作第几个信号量(从 0 开始)
cmd控制命令
常用cmd作用
SETVAL设置信号量的值(通过第四个参数)
GETVAL获取信号量的值(返回值)
IPC_RMID删除信号量集
// 设置信号量值为 1 semctl(semid, 0, SETVAL, 1); // 获取信号量当前值 int val = semctl(semid, 0, GETVAL); // 删除信号量集 semctl(semid, 0, IPC_RMID);
3.结构体sembuf—— 定义信号量操作
struct sembuf { unsigned short sem_num; // 操作第几个信号量(从0开始) short sem_op; // 操作数(正/负/零) short sem_flg; // 操作标志(阻塞/非阻塞) };
sem_flg常用值说明
0默认:阻塞等待
IPC_NOWAIT非阻塞:不等待,立即返回错误
SEM_UNDO进程退出时自动撤销对该信号量的操作(防止死锁)
sem_op效果
负数(-1)P操作:获取资源(信号量值减少),若值不够则阻塞
正数(+1)V操作:释放资源(信号量值增加),唤醒等待者
0等待信号量值为 0

执行过程:

  1. 通过semid在内核中找到对应的信号量对象

  2. 从联合体a中取出val的值(1)

  3. 把这个值赋给找到的信号量

  4. 信号量的状态从原来的值(可能是0)变成了1
  • P = 获取资源,把信号量从"未占用"改为"占用"
    V = 释放资源,把信号量从"占用"改为"未占用"
  • struct sembuf完整结构,用于描述对信号量的具体操作

面试题

例题:进程a和进程b模拟访问打印机,进程a输出第一个字符‘a’表示开始使用打印
机,输出第二个字符‘a’表示结束使用,b进程操作与进程相同。由于打印机同一时刻
只能被一个进程使用,所以输出结果不应该出现abab),

c#a.c

C#b.c代码实现:

生产者-消费者 vs 读者-写者

一、核心关系对比

维度生产者-消费者读者-写者
核心冲突速率不匹配(生产太快/消费太慢)读写冲突(数据一致性)
角色生产数据 vs 消费数据读数据 vs 写数据
并发允许生产和消费不能同时操作同一个槽位读-读可并发,读-写互斥,写-写互斥

二、同步条件

生产者-消费者读者-写者
缓冲区不满 → 才能生产无写者 → 读者可读
缓冲区不空 → 才能消费无读者/写者 → 写者可写
互斥访问缓冲池写者优先级通常高于读者(防止写饿死)

三、信号量设计

生产者-消费者读者-写者
empty:空位数(生产者关心)wrt:写互斥(读写/写写互斥)
full:数据数(消费者关心)mutex:保护读者计数
mutex:保护缓冲池readcount:当前读者数

四、典型应用

生产者-消费者读者-写者
任务队列、日志缓冲、管道数据库读写锁、文件系统缓存

总结

生产者-消费者管的是速率匹配(队列空/满),用empty/full两个信号量;

读者-写者管的是访问权限(读读共享,读写互斥),用wrt保证写独占。

Reactor 模型

生产者-消费者角色Reactor 中的对应组件说明
生产者主线程(Reactor 线程)通过epoll_wait监听事件,一旦有 I/O 就绪,就生产一个“任务”
产品/任务就绪事件(fd 可读/可写)一个“连接可读”或“连接可写”的事件
缓冲队列请求队列主线程将事件放入队列,工作线程从队列取
消费者工作线程(Worker 线程)从队列中取事件,执行 Read、Process、Write

映射关系

主线程(生产者) │ ├── epoll_wait() → 监听事件(原材料) ├── 插入就绪的事件 → 生产任务(放入队列) └── 请求队列 → 缓冲区(任务队列) │ ▼ 工作线程(消费者) ├── Read → 读数据 ├── Process → 处理业务 └── Write → 写回客户端

Reactor 的变体分类

变体说明生产者-消费者体现
单 Reactor 单线程一个线程既负责监听,又负责处理生产者和消费者是同一个线程(串行)
单 Reactor 多线程主线程负责监听,工作线程负责处理主线程是生产者,工作线程是消费者
多 Reactor 多线程多个 Reactor 线程各自监听和处理多个生产者 + 多个消费者

Reactor 模式 = 主线程(生产者)用 epoll 生产事件 + 请求队列(缓冲区)+ 工作线程(消费者)处理事件。

这就是生产者-消费者模型在网络编程中的具体应用

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询