一.信号量
1.1信号量概念
信号量是一种同步机制,用于控制对共享资源的访问。它是由计算机科学家 Edsger Dijkstra 在 1965 年提出的。
信号量可以理解为一个计数器,它可以表示可用资源的数量,可以是整数类型。信号量有两个主要操作:P(等待)和 V(释放)。
-
P(等待)操作:当一个进程或线程需要访问共享资源时,它会尝试执行 P 操作。如果信号量的值大于 0,则进程可以继续访问资源,并将信号量的值减 1;如果信号量的值等于 0,则进程会被阻塞,直到信号量的值变为正数。
-
V(释放)操作:当一个进程或线程完成对共享资源的访问时,它会执行 V 操作,将信号量的值加 1。如果有其他等待进程被阻塞,它们中的一个将被唤醒并获得对资源的访问权限。
信号量主要用于避免多个进程或线程同时访问共享资源导致的竞态条件。通过合理地控制信号量的值,可以实现对共享资源的互斥访问和同步操作。
此外,信号量还可以用于解决生产者-消费者问题、读者-写者问题等并发编程中的同步和互斥需求。
需要注意的是,信号量的正确使用需要仔细设计和管理,以避免死锁和竞态条件的发生。因此,在使用信号量时,应该了解其概念,并根据具体的应用场景进行合理的使用和配置。
1.2信号量编程步骤
使用信号量进行编程时,可以按照以下步骤进行:
-
导入信号量库:根据所使用的编程语言,首先需要导入相关的信号量库或模块。
-
创建信号量:使用相应的函数或方法创建一个信号量对象,并指定初始值。初始值表示可用资源的数量。
-
P(等待)操作:当一个进程或线程需要使用共享资源时,执行 P 操作,尝试获取信号量。具体的实现细节可能因编程语言不同而有所差异,通常使用对应的函数或方法。
-
判断信号量值:在执行 P 操作后,判断信号量的值。如果信号量的值大于 0,则获得了对共享资源的访问权限;如果信号量的值等于 0,则进程或线程会被阻塞,直到信号量的值变为正数。
-
访问共享资源:在获得对共享资源的访问权限后,执行需要的操作,可以是对共享资源的读取或修改。
-
V(释放)操作:完成对共享资源的访问后,执行 V 操作,释放信号量。这将增加信号量的值。
以上是使用信号量的基本编程步骤。需要注意的是,对于不同的编程语言和库,具体的函数和方法名称可能会有所差异,因此请参考相应的文档和资源以确保正确使用信号量机制。
此外,在使用信号量时,还应注意避免出现死锁和竞态条件等并发编程中的常见问题。合理地设计和管理信号量的使用,以确保资源访问的互斥性和同步性,是编程中的重要考虑因素。
1.3函数接口
1.ftok
ftok
函数是一个用于生成 System V IPC 键值 (key) 的函数,它可以将一个已存在的文件的路径名(或其他非零字符串)和一个整数项目标识符(如进程 ID)组合生成一个唯一的键值。
函数原型如下:
key_t ftok(const char *pathname, int proj_id);
pathname
:一个已存在的文件路径名,用于生成 IPC 键值;文件必须对调用进程可读。proj_id
:一个非零的整数项目标识符,可以是任意值,用来区分不同的 IPC 对象。
ftok
函数根据给定的文件路径名和项目标识符计算出一个唯一的键值(key),该键值可以用于创建或访问 System V 信号量、共享内存和消息队列等 IPC 对象。
需要注意的是,使用 ftok
函数生成的键值并不保证在不同的系统上相同,因此,如果需要跨平台的 IPC 通信,建议使用其他方式生成键值。
示例用法:
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
int main() {
key_t key = ftok("/path/to/file", 'A');
if (key == -1) {
perror("ftok");
return -1;
}
printf("Generated key: %x\n", key);
return 0;
}
以上示例中,通过给定文件路径 /path/to/file
和项目标识符 'A'
,使用 ftok
函数生成了一个对应的键值。生成的键值将会以 16 进制形式打印出来。
2.semget
semget()
函数用于创建或访问一个 System V 信号量集。它返回一个与指定键值关联的信号量集标识符(semid)。如果已存在具有相同键值的信号量集,则返回其对应的标识符。如果不存在,则根据给定的键值创建一个新的信号量集。
函数原型如下:
int semget(key_t key, int nsems, int semflg);
key
:用于标识信号量集的键值。通常可以使用ftok
函数生成。nsems
:指定将创建或访问的信号量集中的信号量数量。semflg
:标志参数,用于指定信号量集的创建和访问方式。
返回值:
- 成功:返回一个大于等于 0 的信号量集标识符(semid)。
- 失败:返回 -1,并设置 errno 来指示错误。
使用 semget()
函数创建新的信号量集时,需要满足以下条件:
- 要创建的信号量集不存在具有相同键值的信号量集。
key
为 IPC_PRIVATE,表示创建一个私有的信号量集。
常用的 semflg
标志参数如下:
IPC_CREAT
:如果与给定键值关联的信号量集不存在,则创建一个新的信号量集。IPC_EXCL
:与IPC_CREAT
参数一起使用,如果该信号量集已经存在,则创建失败。
示例用法:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
int main() {
int semid;
key_t key = ftok("/path/to/file", 'A');
if (key == -1) {
perror("ftok");
return -1;
}
semid = semget(key, 3, IPC_CREAT | IPC_EXCL | 0644);
if (semid == -1) {
perror("semget");
return -1;
}
printf("Semaphore ID: %d\n", semid);
return 0;
}
以上示例中,通过 ftok
函数生成一个键值,然后使用 semget
函数创建一个具有 3 个信号量的新信号量集。如果已存在具有相同键值的信号量集,创建会失败。最后,打印生成的信号量集标识符(semid)。
3.semctl
semctl()
函数用于对 System V 信号量集进行控制操作,如获取信号量的状态、设置信号量的值、删除信号量集等。它可以实现对单个信号量或整个信号量集的操作。
函数原型如下:
int semctl(int semid, int semnum, int cmd, ...);
semid
:目标信号量集的标识符。semnum
:用于指定要操作的具体信号量的索引。对于整个信号量集的操作,此参数一般设置为 0。cmd
:指定要执行的控制操作。
semctl()
函数的第四个参数为可选参数,根据不同的 cmd
命令,它的类型和用法可能不同。常用的 cmd
控制命令如下:
IPC_STAT
:获取信号量集的状态信息,将结果存储在semun
结构体中。SETVAL
:设置单个信号量的值。第四个参数为一个新的值,可以是整数或结构体semun
。GETVAL
:获取单个信号量的值。IPC_RMID
:删除整个信号量集。
示例用法:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <errno.h>
int main() {
key_t key = ftok("/path/to/file", 'A');
if (key == -1) {
perror("ftok");
return -1;
}
int semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0644);
if (semid == -1) {
perror("semget");
return -1;
}
// 设置信号量的值
union semun arg;
arg.val = 5;
int result = semctl(semid, 0, SETVAL, arg);
if (result == -1) {
perror("semctl");
return -1;
}
// 获取信号量的值
int val = semctl(semid, 0, GETVAL);
if (val == -1) {
perror("semctl");
return -1;
}
printf("Semaphore value: %d\n", val);
// 删除信号量集
result = semctl(semid, 0, IPC_RMID);
if (result == -1) {
perror("semctl");
return -1;
}
return 0;
}
以上示例中,通过 ftok
函数生成一个键值,然后使用 semget
函数创建一个具有一个信号量的新信号量集。接着,使用 semctl
函数分别设置该信号量的值为 5,并获取其值并打印。最后,使用 semctl
函数将删除信号量集。
需要注意的是,semctl
函数的错误处理需要通过返回值判断并使用 perror
函数打印错误信息。另外,对于复杂的操作,可能需要使用结构体 semid_ds
和 semun
,详细使用方法需要参考相关文档和手册。
4.semop
semop()
函数用于执行一系列的信号量操作,即对 System V 信号量集进行 P(等待)和 V(释放)操作,实现进程间的互斥和同步。
函数原型如下:
int semop(int semid, struct sembuf *sops, size_t nsops);
semid
:目标信号量集的标识符。sops
:指向sembuf
结构体数组的指针,每个结构体表示一个信号量操作。nsops
:指定要执行的信号量操作的数量。
sembuf
结构体定义如下:
struct sembuf {
unsigned short sem_num; // 信号量的索引
short sem_op; // 信号量操作
short sem_flg; // 操作标志
};
sem_num
:要操作的信号量的索引。sem_op
:表示信号量的操作,负值表示 P(等待)操作,正值表示 V(释放)操作,0 表示等待信号量值为 0。sem_flg
:操作标志,控制操作行为。常用的标志包括 IPC_NOWAIT(非阻塞)和 SEM_UNDO(系统恢复时撤消操作)等。
示例用法:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <errno.h>
int main() {
key_t key = ftok("/path/to/file", 'A');
if (key == -1) {
perror("ftok");
return -1;
}
int semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0644);
if (semid == -1) {
perror("semget");
return -1;
}
// 设置信号量的初始值为 1
union semun arg;
arg.val = 1;
int result = semctl(semid, 0, SETVAL, arg);
if (result == -1) {
perror("semctl");
return -1;
}
// 等待信号量的值为 0
struct sembuf sop;
sop.sem_num = 0;
sop.sem_op = 0;
sop.sem_flg = 0;
result = semop(semid, &sop, 1);
if (result == -1) {
perror("semop");
return -1;
}
// 对信号量做 P 操作
sop.sem_op = -1;
result = semop(semid, &sop, 1);
if (result == -1) {
perror("semop");
return -1;
}
printf("Semaphore operation complete.\n");
// 对信号量做 V 操作
sop.sem_op = 1;
result = semop(semid, &sop, 1);
if (result == -1) {
perror("semop");
return -1;
}
// 删除信号量集
result = semctl(semid, 0, IPC_RMID);
if (result == -1) {
perror("semctl");
return -1;
}
return 0;
}
以上示例中,通过 ftok
函数生成一个键值,然后使用 semget
函数创建一个具有一个信号量的新信号量集。接着,使用 semctl
函数设置该信号量的初始值为 1。然后,使用 semop
函数对信号量进行操作,先等待信号量的值为 0,再执行 P(等待)操作,最后执行 V(释放)操作。最后,通过 semctl
函数删除信号量集。
需要注意的是,semop
函数的错误处理需要通过返回值判断并使用 perror
函数打印错误信息。另外,对于复杂的操作,可能需要使用多个 sembuf
结构体来执行一系列的信号量操作。详细使用方法需要参考相关文档和手册。
1.4编程实战
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <stdlib.h>
union semun {
int val;
struct semid_ds *buf;
};
void mysemop(int semid, int num, int op)
{
struct sembuf buf;
buf.sem_flg = 0; //堵塞
buf.sem_num = num; //信号灯的编号
buf.sem_op = op; //pv操作
semop(semid, &buf, 1);
}
int main(int argc, char const *argv[])
{
//1.产生一个唯一的key值
key_t key = ftok("/home/hq/demo/进程/a.txt", 'B');
if (key < 0)
{
perror("ftok is err");
exit(0);
}
//2.创建或打开信号灯集
int semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
if (semid < 0)
{
if (errno == 17)
semget(key, 2, 0666);
else
{
perror("semget is err");
exit(0);
}
}
//3.初始化信号灯集
union semun sem;
sem.val = 0;
semctl(semid, 0, SETVAL, sem);
sem.val = 10;
semctl(semid, 1, SETVAL, sem);
//3.pv操作
int i = 6;
while (i--)
{
mysemop(semid, 1, -1);
printf("%d\n", semctl(semid, 1, GETVAL));
}
//4.删除信号灯集
semctl(semid, 0, IPC_RMID);
return 0;
}