1 信号量
信号量与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制。相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志。除了用于访问控制外,还可用于进程同步。信号量有以下两种类型:
二值信号量:最简单的信号量形式,信号灯的值只能取 0 或 1,类似于互斥锁。
计算信号量:信号量的值可以取任意非负值(当然受内核本身的约束)
信号量只能进行两种操作等待和发送信号,即 P(sv)和 V(sv),他们的行为是这样的:
P(sv):如果 sv 的值大于零,就给它减 1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待 sv 而被挂起,就让它恢复运行,如果没有进程因等待 sv 而挂起,就给它加 1.
POSIX 信号量 目前编程中比较常用的模块。
2 POSIX 信号量
2.1 特点
它有两个优势:
- 现代标准:遵循 POSIX 标准(<semaphore.h>)。
- 轻量级:设计更简洁,支持线程和进程间同步。
POSIX 信号量有两种类型:
- 命名信号量(Named Semaphore):通过文件名标识,可用于无亲缘关系的进程。
- 匿名信号量(Unnamed Semaphore):存在于共享内存中,通常用于线程或多进程(需共享内存)。
2.2 涉及到函数
POSIX 信号量 的主要函数如下所示, 将在下面的章节中进行具体介绍。
3 命名信号量
命名信号量可以在不同进程之间共享,可以没有亲缘关系的进程李米娜进行通讯。
3.1 相关函数
3.1.1 sem_open() 函数
sem_open() 是 POSIX 信号量 API 中的一个函数,用于创建或打开一个命名信号量。
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
参数如下:
name:信号量的名字。必须以 / 开头,例如 /my_sem。
实际文件存储在 /dev/shm/sem.mysem。
名称长度限制(通常 NAME_MAX-4,即 251 字符)
oflag:打开标志,可以是以下值的组合:
O_CREAT:如果信号量不存在,则创建它。
O_EXCL:与 O_CREAT 一起使用,如果信号量已存在,则返回错误。
mode:信号量的权限(类似于文件权限),例如 0666。
value:信号量的初始值(仅在创建信号量时有效)。 一般为1
返回值:
成功时返回指向信号量的指针。
失败时返回 SEM_FAILED,并设置 errno。
3.1.2 sem_close() 和 sem_unlink() - 清理资源
sem_unlink 是 POSIX 信号量(Named Semaphore) 的清理函数,用于 从文件系统中删除命名信号量的标识。它不会立即销毁信号量,而是标记删除,当所有引用该信号量的进程关闭后,内核会自动释放资源。
先要关闭信号量,删除信号量。
#include <semaphore.h>
int sem_close(sem_t *sem); // 关闭信号量(进程内)
int sem_unlink(const char *name); // 删除信号量(文件系统)
删除信号量的函数介绍如下:
#include <semaphore.h>
int sem_unlink(const char *name);
name 命名信号量的文件系统路径(如 “/mysem”),必须以 / 开头。
返回值
成功:返回 0。
失败:返回 -1 并设置 errno(如 ENOENT 表示信号量不存在)
3.1.3 sem_wait() 和 sem_post() - P/V 操作
int sem_wait(sem_t *sem); // P操作(阻塞)
int sem_post(sem_t *sem); // V操作(释放)
3.2 命名信号量的使用示例
父子进程同时打印数据,通过互斥量保护打印数据。
#include <stdio.h>
#include <fcntl.h>
#include <semaphore.h>
#include <unistd.h>int main() {const char *sem_name = "/my_sem"; // 信号量名称sem_t *sem = sem_open(sem_name, O_CREAT, 0644, 1); // 创建信号量,初始值为 1if (sem == SEM_FAILED) {perror("sem_open");return 1;}pid_t pid = fork();if (pid == 0) {// 子进程sem_wait(sem); // 等待信号量(P 操作)printf("Child process acquired the semaphore\n");sleep(2); // 模拟临界区操作printf("Child process releasing the semaphore\n");sem_post(sem); // 释放信号量(V 操作)} else if (pid > 0) {// 父进程sem_wait(sem); // 等待信号量(P 操作)printf("Parent process acquired the semaphore\n");sleep(2); // 模拟临界区操作printf("Parent process releasing the semaphore\n");sem_post(sem); // 释放信号量(V 操作)wait(NULL); // 等待子进程结束sem_close(sem); // 关闭信号量sem_unlink(sem_name); // 删除信号量} else {perror("fork");return 1;}return 0;
}
4 未命名信号量
未命名信号量(Unnamed Semaphore)是 POSIX 信号量 的一种,仅通过内存地址访问(不依赖文件系统路径),适用于 线程间同步 或 共享内存的进程间同步。
4.1 核心函数
常见函数如下所示:
4.1.1 sem_init 函数
创建信号量的, 一般初始值为1。
int sem_init(sem_t *sem, int pshared, unsigned int value);
pshared:0:信号量在线程间共享。
1:信号量在进程间共享(需放在共享内存中)。
value:信号量初始值(如 1 表示二进制信号量)。
4.1.2 sem_destroy()
匿名信号量不会自动释放资源,需显式销毁。
sem_destroy(sem); // 销毁信号量
4.2 参考案例
4.2.1 线程同步
这里创建两个线程。 对线程的资源进行保护。关于线程的介绍,可以参考这篇文章。
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>sem_t sem; // 未命名信号量void* thread_func(void* arg) {sem_wait(&sem); // 等待信号量(P 操作)printf("Thread acquired the semaphore\n");sleep(2); // 模拟临界区操作printf("Thread releasing the semaphore\n");sem_post(&sem); // 释放信号量(V 操作)return NULL;
}int main() {sem_init(&sem, 0, 1); // 初始化未命名信号量,初始值为 1pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);sem_wait(&sem); // 等待信号量(P 操作)printf("Main thread acquired the semaphore\n");sleep(2); // 模拟临界区操作printf("Main thread releasing the semaphore\n");sem_post(&sem); // 释放信号量(V 操作)pthread_join(tid, NULL); // 等待线程结束sem_destroy(&sem); // 销毁信号量return 0;
}
4.2.2 进程同步-共享内存
#include <semaphore.h>
#include <sys/shm.h>
#include <stdio.h>int main() {// 1. 创建共享内存int shmid = shmget(IPC_PRIVATE, sizeof(sem_t), 0666);sem_t *sem = (sem_t*)shmat(shmid, NULL, 0);// 2. 初始化信号量(1表示进程间共享)sem_init(sem, 1, 1); // 初始值=1if (fork() == 0) {// 子进程sem_wait(sem);printf("Child process in critical section\n");sem_post(sem);shmdt(sem);} else {// 父进程sem_wait(sem);printf("Parent process in critical section\n");sem_post(sem);wait(NULL);sem_destroy(sem); // 销毁信号量shmctl(shmid, IPC_RMID, NULL); // 删除共享内存}return 0;
}
5 总结
综合前面两个文章,两个信号量特点如下: