信号量是一个计数器,用于控制多个进程/线程对共享资源的访问。信号量支持两个原子操作:
核心特点:
| 类型 | 计数值范围 | 典型用途 |
|---|---|---|
| 二值信号量 | 0 或 1 | 互斥访问(类似互斥锁) |
| 计数信号量 | 0 ~ N | 限制并发访问数量(如连接池) |
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
// 创建/获取信号量集
int semget(key_t key, int nsems, int semflg);
// 信号量操作
int semop(int semid, struct sembuf *sops, unsigned nsops);
// 信号量控制
int semctl(int semid, int semnum, int cmd, ...);
struct sembuf {
unsigned short sem_num; // 信号量编号(在信号量集中的索引)
short sem_op; // 操作:-1=P操作,+1=V操作,0=等待为0
short sem_flg; // 标志:IPC_NOWAIT(非阻塞),SEM_UNDO(进程退出自动还原)
};
| 命令 | 说明 |
|---|---|
SETVAL |
设置信号量值 |
GETVAL |
获取信号量值 |
IPC_RMID |
删除信号量集 |
GETALL |
获取所有信号量值 |
SETALL |
设置所有信号量值 |
#include <semaphore.h>
// 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
// P操作(等待/减1)
int sem_wait(sem_t *sem);
// 非阻塞P操作
int sem_trywait(sem_t *sem);
// V操作(通知/加1)
int sem_post(sem_t *sem);
// 销毁信号量
int sem_destroy(sem_t *sem);
参数说明:
pshared:0 表示线程间共享,非0 表示进程间共享(需放在共享内存中)sem_open / sem_close / sem_unlink(可用于无亲缘关系的进程)信号量最常见的用途是保护共享内存的并发访问:
进程A 进程B
│ │
├── P(写锁) ────────────>│
│ 写入共享内存 │
├── V(读锁) ────────────>│
│ P(读锁)
│ 读取共享内存
│ V(写锁)
// sem_prodcons.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int in = 0, out = 0;
sem_t sem_empty; // 空闲槽位数
sem_t sem_full; // 已用槽位数
sem_t sem_mutex; // 互斥访问缓冲区
void *producer(void *arg) {
int id = *(int *)arg;
for (int i = 0; i < 10; i++) {
int item = id * 100 + i;
sem_wait(&sem_empty); // 等待空闲槽位
sem_wait(&sem_mutex); // 互斥访问
buffer[in] = item;
in = (in + 1) % BUFFER_SIZE;
printf("[Producer %d] Produced: %d\n", id, item);
sem_post(&sem_mutex); // 释放互斥
sem_post(&sem_full); // 增加已用槽位
usleep(100000); // 模拟生产耗时
}
return NULL;
}
void *consumer(void *arg) {
int id = *(int *)arg;
for (int i = 0; i < 10; i++) {
sem_wait(&sem_full); // 等待有数据
sem_wait(&sem_mutex); // 互斥访问
int item = buffer[out];
out = (out + 1) % BUFFER_SIZE;
printf("[Consumer %d] Consumed: %d\n", id, item);
sem_post(&sem_mutex); // 释放互斥
sem_post(&sem_empty); // 增加空闲槽位
usleep(150000); // 模拟消费耗时
}
return NULL;
}
int main(void) {
pthread_t prod_tid[2], cons_tid[2];
int prod_ids[2] = {1, 2};
int cons_ids[2] = {1, 2};
// 初始化信号量
sem_init(&sem_empty, 0, BUFFER_SIZE); // 初始空闲槽位 = 缓冲区大小
sem_init(&sem_full, 0, 0); // 初始已用槽位 = 0
sem_init(&sem_mutex, 0, 1); // 互斥信号量初始值 = 1
// 创建生产者和消费者线程
for (int i = 0; i < 2; i++) {
pthread_create(&prod_tid[i], NULL, producer, &prod_ids[i]);
pthread_create(&cons_tid[i], NULL, consumer, &cons_ids[i]);
}
// 等待线程结束
for (int i = 0; i < 2; i++) {
pthread_join(prod_tid[i], NULL);
pthread_join(cons_tid[i], NULL);
}
// 销毁信号量
sem_destroy(&sem_empty);
sem_destroy(&sem_full);
sem_destroy(&sem_mutex);
printf("All done.\n");
return 0;
}
编译与运行:
gcc -o sem_prodcons sem_prodcons.c -pthread
./sem_prodcons
// sem_sysv.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/wait.h>
#define SEM_KEY 0x5678
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
void p_operation(int semid, int sem_num) {
struct sembuf op = {sem_num, -1, 0};
if (semop(semid, &op, 1) == -1) {
perror("P operation");
exit(EXIT_FAILURE);
}
}
void v_operation(int semid, int sem_num) {
struct sembuf op = {sem_num, 1, 0};
if (semop(semid, &op, 1) == -1) {
perror("V operation");
exit(EXIT_FAILURE);
}
}
int main(void) {
// 创建信号量集(1个信号量)
int semid = semget(SEM_KEY, 1, IPC_CREAT | 0666);
if (semid == -1) {
perror("semget");
exit(EXIT_FAILURE);
}
// 初始化信号量值为 1(二值信号量)
union semun arg;
arg.val = 1;
if (semctl(semid, 0, SETVAL, arg) == -1) {
perror("semctl SETVAL");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// 子进程:进入临界区
printf("[Child] Waiting for semaphore...\n");
p_operation(semid, 0);
printf("[Child] Entered critical section\n");
sleep(2);
printf("[Child] Leaving critical section\n");
v_operation(semid, 0);
exit(EXIT_SUCCESS);
} else {
// 父进程:等待信号量
printf("[Parent] Waiting for semaphore...\n");
p_operation(semid, 0);
printf("[Parent] Entered critical section\n");
sleep(2);
printf("[Parent] Leaving critical section\n");
v_operation(semid, 0);
wait(NULL);
}
// 删除信号量集
semctl(semid, 0, IPC_RMID);
printf("Semaphore removed\n");
return 0;
}
编译与运行:
gcc -o sem_sysv sem_sysv.c
./sem_sysv
SEM_UNDO 可防止进程异常退出后信号量未释放sem_open)可用于无亲缘关系的进程