设为首页 加入收藏

TOP

信号量的无序竞争和有序竞争(一)
2023-07-23 13:32:12 】 浏览:54
Tags:

以下内容为本人的著作,如需要转载,请声明原文链接 微信公众号「englyf」https://mp.weixin.qq.com/s/lrERW0P7Q6zvklv4JfUO2A


信号量的无序竞争和有序竞争

在linux的多进程(或者多线程,这里以进程为例)开发里经常有进程间的通信部分,常见的技术手段有信号量、消息队列、共享内存等,而共享内存和信号量就像衬衫和外套一样搭配才算完整。

信号量的使用可以使得对资源的访问具有排它性,单一时刻只允许同一个进程访问,而其它的进程统统排队等候或者取消行程打道回府。

对资源的访问权既然要有排它性,那么访问权的获得就必然有竞争关系。竞争关系,又会使得结果是有顺序的,包括有序和无序。无序就是,竞争是公平的,对资源的访问权获取是随机的。而有序则是,对竞争的结果有刻意的安排,出现固定的顺序,比如数据生产消费模型里,数据一般是安排先在生产端输出,然后才轮到消费端访问。

好了,扯得太长太阳都快出来了。

信号量的使用库有System V库和POXIS库两种,这里仅简单介绍System V库和相关API,太详细会让人睡着的。

函数原型 备注
int semget(key_t key, int nsems, int semflg) 获取或者创建一个信号量集的标识符,一个信号量集可以包含有多个信号量,nsems代表信号量数量,key可以通过ftok获取(也可以直接使用IPC_PRIVATE,但是仅能用于父子进程间通信),semflg代表信号量集的属性
int semctl(int semid, int semnum, int cmd, union semun arg) 设置或者读取信号量集的某个信号量的信息,semid代表semget返回值,semnum代表信号量的序号,类型union semun在某些系统中不一定存在(如有需要可以自定义)
int semop(int semid, struct sembuf *sops, unsigned nsops) 执行PV操作,P是对资源的占用,V是对资源的释放,类型struct sembuf包含了操作的具体内容,nsops代表操作信号量的个数(一般仅用1)
struct sembuf {
    short sem_num;   //指定信号量,信号量在信号量集中的序号,从0开始
    short sem_op;    //小于0,就是执行P操作,对信号量减去sem_op的绝对值;大于0,就是执行V操作,对信号量加上sem_op的绝对值;等于0,等待信号量值归0
    short sem_flg;   //0,IPC_NOWAIT,SEM_UNDO(方便于调用进程崩溃时对信号量值的自动恢复,防止对资源的无用挤占)
}

下面介绍一下信号量的两种使用方式。

信号量的无序竞争

信号量最简单的使用方式就是无序的竞争方式。比如在获取资源时,只使用一个信号量,各个进程公平竞争上岗。预设其中一个特定进程启动后,初始化信号量的值为1(调用semctl实现)。然后当所有进程其中的一个需要抢占资源时,P操作对信号量值减1,信号量值归0,调用进程抢占资源成功,资源使用完成后,V操作对信号量值加1,信号量值变为1,释放资源。

当信号量值归0后,其它进程如果需要抢占资源,对信号量执行P操作会导致调用进程挂起并等待,这是调用进程堵塞了。如果执行P操作时,semop的sem_flg用了IPC_NOWAIT,则直接返回-1,通过errno可以获取到错误代码EAGAIN。

PV操作就是通过semop函数对信号量的值检查再加减操作。

老是觉得话太多还不如几行代码来得直接明了。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/sem.h>

void P(int sid)
{
    struct sembuf sem_p;
    sem_p.sem_num = 0;
    sem_p.sem_op = -1;
    sem_p.sem_flg = 0;

    if (semop(sid, &sem_p, 1) == -1) {
        perror("p fail");
        exit(-1);
    }
}

void V(int sid)
{
    struct sembuf sem_v;
    sem_v.sem_num = 0;
    sem_v.sem_op = 1;
    sem_v.sem_flg = 0;

    if (semop(sid, &sem_v, 1) == -1) {
        perror("v fail");
        exit(-1);
    }
}

int main(int argc, char *argv[])
{
    int fd = open("semtest", O_RDWR | O_CREAT, 0666);
    if (fd == -1) {
        perror("open");
        exit(-1);
    }

    key_t key = ftok("semtest", 'a');
    if (key == -1) {
        perror("ftok");
        exit(-1);
    }

    int sid = semget(key, 1, IPC_CREAT | 0666);
    if (sid == -1) {
        perror("semget");
        exit(-1);
    }

    if (semctl(sid, 0, SETVAL, 1) == -1) {
        perror("semctl");
        exit(-1);
    }

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(-1);
    } else if (pid == 0) {
        // child process
        while (1) {
            P(sid);
            printf("child get\n");
            sleep(1);
            printf("child release\n");
            V(sid);
        }
    } else {
        // parent process
        printf("parent pid %d child pid %d\n", getpid(), pid);
        while (1) {
            P(sid);
            printf("parent get\n");
            sleep(1);
            printf("parent release\n");
            V(sid);
        }
    }

    return 0;
}

然后看看结果输出,第一次可能是这样子的

parent pid 13156 child pid 13157
child get
child release
parent get
parent release
child get
child release
parent get
parent release
child get
child release
...

第二次可能就是这样子了

parent pid 12873 child pid 12874
parent get
parent release
child get
child release
parent get
首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇gzip 介绍和使用 下一篇10_Linux基础-SHELL入门1

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目