进程间通信(IPC) --- 信号量

       信号量(semaphore)是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语。信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为 0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒。Posix 信号量分为有名信号量无名信号量(也叫基于内存的信号量)。

       无名信号量,又称为基于内存的信号量,由于其没有名字,没法通过 open 操作直接找到其对应的信号量,所以很难直接用于没有关联的两个进程之间。无名信号量多用于线程之间的同步。

       有名信号量由于其有名字,多个不相干的进程可以通过名字来打开同一个信号量,从而完成同步操作,所以有名信号量的操作要方便一些,适用范围也比无名信号量更广。今天我们就来看看有名信号量的使用。

       创建或打开一个有名信号量,使用 sem_open 函数

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>

sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
  • 名字的命名首字符必须是 ' / '
  • 第二个参数 oflag 标志位支持的标志包括 O_CREAT 和 O_EXCL 标志位,如果带了 O_CREAT 标志位,则表示要创建信号量。 
  • mode 表示创建的新信号量的访问权限,标志位和 open 函数一样,mode 参数的值也会根据进程的 umask 来取掩码。
  • value 是新建信号量的初始值。创建和赋初值都是由一个接口来完成的,这样就不会出现 system V 信号量可能出现的初始化竞争问题了。value 的值在最小值 0 和最大值 SEM_VALUE_MAX 之间,对于 linux 来说,这个限制为 INT_MAX(在 Linux/x86 平台上,该值为 2147483647)。

       当 sem_open 函数失败时,返回 SEM_FAILED,并且设置 errno。

       让我们来创建一个有名信号量

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <fcntl.h>

int main()
{
    sem_t *sem_p = sem_open("/mysem", O_CREAT|O_EXCL, 0644, 1);
    if(sem_p == SEM_FAILED) {
        perror("sem_open failed");
        exit(1);
    }

    return 0;
}

       需要注意的是,因为信号量多用于线程间的通信,因此在编译时要加上 -lpthread 。并在在 linux 下,信号量文件的储存没有自己单独的文件夹,它是储存在共享内存文件的文件夹下的,并且可以看到系统在我们自己的命名前加上了 sem. 的前缀,用来标识这是一个信号量文件。

       当一个进程打开有名信号量时,系统会记录进程与信号的关联关系。调用 sem_close 可以终止这种关联关系,同时信号量的进程数的引用计数减 1 

#include <semaphore.h>

int sem_close(sem_t *sem);

       进程终止时,进程打开的有名信号量会自动关闭。当进程执行 exec 系列函数时,进程打开的有名信号量会自动关闭。但是关闭不等同于删除,如果要彻底删除信号量则需要调用 sem_unlink 函数。将有名信号量的名字作为参数传给 sem_unlink,,该函数会负责将该有名信号量删除。由于系统为信号量维护了引用计数,所以只有当打开信号量的所有进程都关闭了之后,这个信号量才会被真正的删除。

#include <semaphore.h>

int sem_unlink(const char *name);

       sem_wait 函数用于等待信号量,它会将信号量的值减 1,如果调用 sem_wait 函数时,信号量的当前值大于0,那么 sem_wait 函数立即返回,否则 sem_wait 函数陷入阻塞,待信号量的值大于 0 之后,再执行减 1 操作,然后成功返回。

#include <semaphore.h>

int sem_wait(sem_t *sem);

       如果仅仅是尝试等待信号量,而不想陷入阻塞,则可以调用 sem_trywait 函数。sem_trywait 会尝试将信号量的值减 1 ,如果信号量的值大于 0,那么该函数将信号量的值减 1 之后会立刻返回。如果信号量的当前值为 0,那么 sem_trywait 也不会陷入阻塞,而是立刻返回失败,并置 erron 为 EAGAIN。

#include <semaphore.h>

int sem_trywait(sem_t *sem);

       若资源当前不可用,那么 sem_wait 就可能会陷入无限阻塞,而 sem_trywait 则选择立刻返回失败,绝不阻塞。除了这两种选择,系统还提供了第三种选择:有期限等待,即 sem_timedwait 函数。

#include <semaphore.h>

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

       第二个参数为一个绝对时间。可以使用 gettimeofday 函数获取到 struct timeval 类型的当前时间,然后将 timeval 转换成 timespec 类型的结构体,最后在该值上加上想等待的时间。或者调用 clock_ gettime 函数,直接获得 timespec 结构体类型的变量表示当前时刻,然后在结构体上加上想等待的时间,作为第二个参数传给 sem_timedwait 函数。

       如果超过了等待时间,信号量的值仍是 0,那么返回 -1,并置 erron 为 ETIMEOUT。

       sem_post 函数用于发布信号量,表示资源以及使用完毕,可以归还资源了。该函数会使信号量的值加 1。如果发布信号量之前,信号量的值是0,并且已经有进程或线程正等待在信号量上,此时会有一个进程被唤醒,被唤醒的进程会继续 sem_wait 函数的减 1 操作。如果有多个进程正等待在信号量上,那么将无法确认哪个进程会被唤醒。

#include <semaphore.h>

int sem_post(sem_t *sem);

       当函数调用成功时返回 0,失败时返回 -1,并置 errno 。当参数 sem 并不指向合法的信号量时,置 errno 为 EINVAL,当信号量的值超过上限(即超过 INT_ MAX )时,置 errno 为 EOVERFLOW。

       sem_getvalue 函数会返回当前信号量的值,并写入 sval 指向的变量。如果信号量的值大于 0,含义自不必说,但是如果信号量的值等于 0,同时又有很多进程或线程阻塞在信号上,那么应该返回 0 还是返回一个负值——其绝对值等于等待进程的个数?看起来后者更有意义,因为从该值可以获知到竞争的激烈程度,但是Linux还是选择返回0。

#include <semaphore.h>

int sem_getvalue(sem_t *sem, int *sval);

       当 sem_ getvalue 返回时,其返回的值可能已经过时了。从这个意义上讲,该接口的意义并不大。

       下面让我们来尝试使用信号量:

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

int main()
{
    sem_t *sem_p = sem_open("/mysem", O_CREAT|O_EXCL, 0644, 1);
    if(sem_p == SEM_FAILED) {
        perror("sem_open failed");
        exit(1);
    }
    pid_t pid = fork();
    if(pid < 0) {
        perror("fork failed");
        exit(2);
    } else if(pid == 0) {
        sem_wait(sem_p);
        printf("Child %d: I have got the sem, I want to sleep...\n", getpid());
        sleep(10);
        sem_post(sem_p);
        sem_close(sem_p);
    } else {
        sleep(1);        // 保证子进程先获得时间片
        sem_wait(sem_p);
        printf("Father %d: I have got the sem finally\n", getpid());
        sem_close(sem_p);
    }
    sem_unlink("/mysem");
    return 0;
}

       运行程序可以看到,在子进程没有执行结束之前,父进程必须一直等待

       当子进程结束之后,父进程才可以开始执行

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值