POSIX 信号量(Semaphore)
一、POSIX 信号量基础
1. 什么是信号量?
- 信号量 是一种同步机制,用于控制对共享资源的访问。它通过一个整数值表示可用资源的数量,支持两种原子操作:
- P操作(Wait):尝试减少信号量值(若值>0,则减1;否则阻塞)。
- V操作(Post):增加信号量值(唤醒等待的线程/进程)。
2. POSIX 信号量的两种类型
类型 | 应用场景 | 特点 |
---|---|---|
命名信号量 | 进程间同步 | 通过文件名全局标识 |
未命名信号量 | 线程间同步(或进程内) | 需通过共享内存传递 |
二、命名信号量(进程间同步)
1. 核心函数
sem_open()
:创建或打开命名信号量sem_wait()
:P操作(阻塞)sem_post()
:V操作(释放)sem_close()
:关闭信号量sem_unlink()
:销毁信号量
2. 示例:两个进程同步访问文件
进程A(写入数据):
#include <fcntl.h>
#include <semaphore.h>
#include <stdio.h>int main() {sem_t *sem = sem_open("/my_named_sem", O_CREAT, 0666, 1); // 初始值为1if (sem == SEM_FAILED) {perror("sem_open failed");return -1;}sem_wait(sem); // 获取信号量(P操作)FILE *fp = fopen("shared.txt", "a");fprintf(fp, "Process A writes.\n");fclose(fp);sem_post(sem); // 释放信号量(V操作)sem_close(sem);sem_unlink("/my_named_sem"); // 注意:仅在最后一个进程调用后销毁return 0;
}
进程B(读取数据):
sem_t *sem = sem_open("/my_named_sem", 0); // 打开已有信号量
sem_wait(sem);
FILE *fp = fopen("shared.txt", "r");
char buf[100];
fgets(buf, 100, fp);
printf("Read: %s", buf);
fclose(fp);
sem_post(sem);
sem_close(sem);
三、未命名信号量(线程间同步)
1. 核心函数
sem_init()
:初始化信号量(需指定线程共享标志)sem_destroy()
:销毁信号量
2. 示例:多线程任务池
#include <pthread.h>
#include <semaphore.h>
#define MAX_TASKS 5sem_t task_sem; // 未命名信号量
int task_queue[MAX_TASKS];
int task_count = 0;void* worker_thread(void* arg) {while (1) {sem_wait(&task_sem); // 等待任务// 取出任务并处理int task = task_queue[--task_count];printf("Processing task: %d\n", task);}return NULL;
}void add_task(int task) {task_queue[task_count++] = task;sem_post(&task_sem); // 发布新任务
}int main() {sem_init(&task_sem, 0, 0); // 初始值为0(无任务)pthread_t tid;pthread_create(&tid, NULL, worker_thread, NULL);// 添加任务for (int i = 0; i < 10; i++) {add_task(i);sleep(1);}sem_destroy(&task_sem);return 0;
}
四、关键注意事项
1. 信号量 vs 互斥锁
特性 | 信号量 | 互斥锁 |
---|---|---|
资源数量 | 可设置初始值(N个资源) | 仅1(互斥) |
所有者 | 无所有者概念 | 锁定者必须负责解锁 |
跨进程 | 支持(命名信号量) | 需进程共享的互斥锁 |
2. 常见陷阱
- 死锁:多个信号量未按顺序获取。
- 解决:统一资源申请顺序。
- 资源泄漏:未正确调用
sem_close()
或sem_unlink()
。- 命名信号量会残留于
/dev/shm
(Linux)。
- 命名信号量会残留于
- 虚假唤醒:
sem_wait()
可能被信号中断。- 建议配合循环检查实际条件。
五、高级应用场景
1. 有限资源池(如数据库连接)
sem_t db_conn_sem;
sem_init(&db_conn_sem, 0, 10); // 最多10个连接void query_database() {sem_wait(&db_conn_sem); // 获取连接// 执行查询...sem_post(&db_conn_sem); // 释放连接
}
2. 多进程生产者-消费者
// 共享内存中定义循环队列和信号量
struct {int buffer[BUFFER_SIZE];int in, out;sem_t empty, full;
} *shared;// 生产者进程
sem_wait(&shared->empty);
shared->buffer[shared->in] = data;
shared->in = (shared->in + 1) % BUFFER_SIZE;
sem_post(&shared->full);// 消费者进程
sem_wait(&shared->full);
data = shared->buffer[shared->out];
shared->out = (shared->out + 1) % BUFFER_SIZE;
sem_post(&shared->empty);
六、代码实战:跨进程聊天程序
1. 设计思路
- 使用 命名信号量 控制消息队列的访问。
- 共享内存存储消息缓冲区。
- 两个进程交替发送和接收消息。
2. 核心代码片段
// 共享内存结构
struct chat_buffer {char message[256];sem_t send_sem, recv_sem;
};// 进程A(先发送)
struct chat_buffer *buf = mmap(...);
sem_init(&buf->send_sem, 1, 1); // 初始可发送
sem_init(&buf->recv_sem, 1, 0); // 初始不可接收while (1) {sem_wait(&buf->send_sem);fgets(buf->message, 256, stdin);sem_post(&buf->recv_sem);
}// 进程B(先接收)
sem_wait(&buf->recv_sem);
printf("Received: %s", buf->message);
sem_post(&buf->send_sem);
七、总结
- POSIX 信号量 是强大的同步工具,适用于线程和进程间的复杂协调。
- 命名信号量 通过文件系统标识,适合进程间同步。
- 未命名信号量 更轻量,但需手动管理内存共享。
- 始终注意 资源释放 和 死锁预防,结合日志或调试工具验证同步逻辑。