切勿好高骛远,任何大事,都是一砖一瓦垒起来的 ——家驹(StrangeHead)
上一篇文章我们知道了什么是信号,以及如何使用POSIX信号量,什么是P操作,什么是V操作,了解过Linux进化史的小伙伴就知道,POSIX是Unix的一个标准规范(Unix及其衍生系统遵循的一系列标准的集合),而System V也只不过是别家公司基于Linux写的另一套信号量罢了。回顾一下上一节的知识,接下来这一节对你来说简直是轻松驾驭!
在并发编程中,尤其是在多进程环境下,资源共享是一个常见问题。为了避免多个进程同时访问某个共享资源,导致数据不一致或系统崩溃,System V 信号量应运而生!信号量是一种原始的同步机制,可以在多个进程间进行通信和控制资源访问。
System V 信号量(sem_t)是 Unix/Linux 系统中一种进程间同步和互斥的机制。它们通常用于解决 互斥(保证一次只有一个进程访问资源)和 同步(控制多个进程的执行顺序)问题。
系统提供了几个关键的函数来操作信号量。它们分别是:
semget()
– 获取信号量集的标识符。semctl()
– 控制信号量集的属性。semop()
– 执行信号量操作(P 操作和 V 操作)。semctl()
– 删除信号量集。semget()
— 创建或获取信号量集int semget(key_t key, int nsems, int semflg);
示例:
int sem_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);
这将创建一个包含 1 个信号量的信号量集,并将其标识符保存在 sem_id 中。信号量集权限为 0666,表示所有用户均可读写。
IPC_PRIVATE
表示创建一个新的信号量集,或者指定一个特定的键值。IPC_CREAT
来创建一个新的信号量集,或者使用 IPC_EXCL
保证不会与已有的信号量集冲突。IPC_CREAT:创建一个新的信号量集。
IPC_EXCL:如果信号量集已经存在,返回错误。
0666:信号量集的权限,类似文件权限。
semop()
— 执行信号量操作int semop(int semid, struct sembuf *sops, size_t nsops);
示例:
struct sembuf sops;
sops.sem_num = 0; // 第一个信号量
sops.sem_op = -1; // P 操作(减 1,表示请求资源)
sops.sem_flg = 0;
if (semop(sem_id, &sops, 1) == -1) {
perror("semop");
exit(1);
}
该代码会让当前进程等待,直到第一个信号量的值大于 0。
semid: 信号量集的标识符。
sops: 指向一个 struct sembuf
数组的指针,表示一系列信号量操作。每个 sops
包含:
sem_num:操作的信号量编号,表示要操作的信号量。
sem_op:操作类型:
sem_flg:通常为 0,表示没有额外标志。
nsops: 操作的数量。
返回值:成功时返回 0,失败时返回 -1。
失败时,返回 -1,并设置 errno。
semctl()
— 控制信号量集int semctl(int semid, int semnum, int cmd, ...);
示例:
// 设置信号量的初始值
if (semctl(sem_id, 0, SETVAL, 1) == -1) {
perror("semctl");
exit(1);
}
// 获取信号量的值
int val = semctl(sem_id, 0, GETVAL);
if (val == -1) {
perror("semctl");
exit(1);
}
printf("信号量值为:%d\n", val);
IPC_RMID
:删除信号量集。GETVAL
:获取信号量当前的值。SETVAL
:设置信号量的值。返回值:根据命令不同,返回不同的值。
SETVAL
返回旧的信号量值,GETVAL
返回信号量的当前值。semctl()
— 删除信号量集通过 semctl()
以 IPC_RMID
命令删除信号量集。
我们通过一个简单的程序来演示如何创建信号量、执行 P 操作、V 操作,以及如何删除信号量集。
#include
#include
#include
#include
#include
int main() {
// 创建一个信号量集,包含 1 个信号量
int sem_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);
if (sem_id == -1) {
perror("semget");
exit(1);
}
// 初始化信号量为 1
if (semctl(sem_id, 0, SETVAL, 1) == -1) {
perror("semctl");
exit(1);
}
struct sembuf sops;
sops.sem_num = 0; // 使用第一个信号量
sops.sem_flg = 0; // 不使用其他标志位
// P 操作:请求资源,信号量减 1
sops.sem_op = -1; // 这里是 P 操作(信号量减 1)
if (semop(sem_id, &sops, 1) == -1) {
perror("semop");
exit(1);
}
printf("进程 %d 获取资源,开始工作...\n", getpid());
sleep(2); // 模拟处理资源的时间
// V 操作:释放资源,信号量加 1
sops.sem_op = 1; // 这里是 V 操作(信号量加 1)
if (semop(sem_id, &sops, 1) == -1) {
perror("semop");
exit(1);
}
printf("进程 %d 释放资源,结束工作!\n", getpid());
// 删除信号量集
if (semctl(sem_id, 0, IPC_RMID) == -1) {
perror("semctl");
exit(1);
}
return 0;
}
semget()
:创建一个信号量集,信号量的数量为 1(即一个资源)。semctl()
:初始化信号量的值为 1,表示资源初始状态为可用。semop()
:执行 P 操作,使得进程占用资源。若信号量为 0,进程将被阻塞。sleep(2)
:模拟进程使用资源的时间。semop()
:执行 V 操作,释放资源,将信号量值加 1,唤醒其他等待的进程。semctl()
:删除信号量集,释放资源。在这个经典问题中,生产者不断生产资源,消费者不断消费资源,我们使用信号量来控制生产者和消费者的同步。
下一节我们使用System V来实现这个进阶实例
System V 信号量是 Linux 系统中提供的一种强大的并发控制工具。通过 semget()
创建信号量集,利用 semop()
执行操作,使用 semctl()
控制信号量的值,进程间可以高效地同步和互斥。对于初学者来说,理解信号量的基本操作和应用场景(如生产者-消费者问题)是学习多进程编程的重要一步。