POSIX IPC:消息队列与信号量
大家好,我是你们的老朋友。今天我们来聊聊 POSIX IPC 的两员大将——消息队列和信号量。说实话,我在刚接触这部分内容时,也曾在 System V 和 POSIX 之间犹豫过。但用久了你会发现,POSIX 接口的设计思路更贴近现代编程习惯。
POSIX 消息队列:轻量级的进程对话
消息队列,说白了就是让进程之间能互相递纸条。POSIX 版本比 System V 的接口更简洁,也更安全。
核心函数速览
| 函数 | 作用 | 我常用的场景 |
|---|---|---|
mq_open() |
创建或打开消息队列 | 服务端初始化时调用 |
mq_send() |
发送消息 | 生产者线程写入数据 |
mq_receive() |
接收消息 | 消费者线程读取数据 |
mq_close() |
关闭描述符 | 进程退出前清理 |
mq_unlink() |
删除队列 | 彻底销毁时使用 |
关键点:消息队列是内核持久化的。进程退出后,队列还在。只有调用 mq_unlink() 才会真正删除。
一个完整的例子
#include <mqueue.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main() {
struct mq_attr attr;
attr.mq_flags = 0;
attr.mq_maxmsg = 10; // 最多10条消息
attr.mq_msgsize = 256; // 每条最大256字节
attr.mq_curmsgs = 0;
// 创建消息队列
mqd_t mq = mq_open("/myqueue", O_CREAT | O_RDWR, 0644, &attr);
if (mq == (mqd_t)-1) {
perror("mq_open");
return 1;
}
// 发送消息
char *msg = "Hello from POSIX!";
if (mq_send(mq, msg, strlen(msg) + 1, 0) == -1) {
perror("mq_send");
}
// 接收消息
char buf[256];
unsigned int prio;
ssize_t n = mq_receive(mq, buf, sizeof(buf), &prio);
if (n != -1) {
printf("收到: %s (优先级: %u)\n", buf, prio);
}
mq_close(mq);
mq_unlink("/myqueue");
return 0;
}
我的小技巧:消息优先级用 0 表示普通消息。数值越大优先级越高。我曾经在一个日志收集系统里,用高优先级发告警消息,低优先级发普通日志,效果很好。
POSIX 信号量:轻量级的同步工具
信号量就是用来控制多个进程对共享资源的访问。POSIX 信号量分两种:命名信号量和无名信号量。今天重点讲命名信号量,因为它可以在不相关的进程间使用。
核心函数
sem_open()— 创建或打开信号量sem_wait()— 减1操作(P操作),如果为0则阻塞sem_post()— 加1操作(V操作),唤醒等待者sem_close()— 关闭信号量sem_unlink()— 删除信号量
注意:sem_wait() 可能会被信号中断返回 EINTR。我早期写代码时没处理这个,结果线上偶发死锁。后来统一加了重试逻辑。
信号量使用示例
#include <semaphore.h>
#include <fcntl.h>
#include <stdio.h>
#include <pthread.h>
sem_t *sem;
void* worker(void* arg) {
int id = *(int*)arg;
sem_wait(sem); // 请求资源
printf("线程 %d 进入临界区\n", id);
sleep(1); // 模拟工作
printf("线程 %d 离开临界区\n", id);
sem_post(sem); // 释放资源
return NULL;
}
int main() {
// 创建信号量,初始值为2(允许2个并发)
sem = sem_open("/mysem", O_CREAT, 0644, 2);
if (sem == SEM_FAILED) {
perror("sem_open");
return 1;
}
pthread_t t1, t2, t3;
int id1 = 1, id2 = 2, id3 = 3;
pthread_create(&t1, NULL, worker, &id1);
pthread_create(&t2, NULL, worker, &id2);
pthread_create(&t3, NULL, worker, &id3);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
sem_close(sem);
sem_unlink("/mysem");
return 0;
}
你想想看,如果初始值设为1,那就变成互斥锁了。设为N,就是N个并发。这就是信号量的灵活之处。
POSIX vs System V:我该怎么选?
很多初学者会纠结这个问题。我个人的经验是:新项目优先用 POSIX。原因如下:
| 对比维度 | POSIX | System V |
|---|---|---|
| 接口风格 | 类文件描述符,简洁 | 用 key_t 和 id,较复杂 |
| 命名方式 | 以 "/" 开头的字符串 | 用 ftok() 生成 key |
| 消息优先级 | 支持,0-31 优先级 | 支持,但类型是 long |
| 超时机制 | mq_timedreceive() 支持 | 不支持,需自己实现 |
| 信号量操作 | 单个信号量操作 | 支持批量操作(semop) |
| 可移植性 | 现代系统都支持 | 老系统兼容性好 |
我的建议:如果你在写新项目,且不需要批量信号量操作,选 POSIX。如果你在维护老代码,或者需要复杂的信号量数组操作,System V 可能更合适。
知识体系总览
下面这张图帮你理清今天讲的内容:
避坑指南
最后分享几个我踩过的坑:
- 消息队列权限问题:创建时记得设置 0644,否则其他进程打不开。我曾经在调试时忘了设权限,排查了半天。
- 信号量初始值:千万别设成0然后直接 sem_wait(),那会永远阻塞。我见过有人把初始值设成0,然后抱怨程序卡死。
- 资源清理:进程崩溃后,消息队列和信号量还留在系统里。可以用
ipcs命令查看,用ipcrm手动清理。 - 命名冲突:POSIX IPC 的名字以 "/" 开头,但不同系统对名字长度有限制。Linux 下建议控制在 255 字节以内。
调试小技巧:用 cat /proc/sys/fs/mqueue/msg_max 查看系统消息队列上限。如果不够用,可以临时修改:echo 100 > /proc/sys/fs/mqueue/msg_max。
好了,今天的内容就到这里。POSIX 消息队列和信号量是进程通信的利器,用好了能写出高效稳定的程序。记住:接口简单不代表功能弱,很多时候简单反而更可靠。