天天看点

进程间同步之--信号量

     信号量分有名和无名信号量。它们的区别和管道及命名管道的区别类似。有名信号量要求创建一个文件,而无名信号量则直接保存在内存中。

一,posix信号量

posex信号量接口总结(见下图):

上面一行是有名信号量,可于fifo相类比,其值保存在文件中,可用于进程和线程同步;

下面一行是无名信号量,可与pipe相类比,其值保存在内存中,可用于进程和线程同步;

中间部分,是两者的公用接口。

1.公共接口

1.1 接口函数说明

#include <semaphore.h>

int sem_wait(sem_t *sem);

    测试所指定信号量的值,它的操作是原子的。

    若sem>0,那么它减1并立即返回。

    若sem==0,则睡眠直到sem>0,此时立即减1,然后返回。   

int sem_trywait(sem_t *sem);

    其他的行为和sem_wait一样,除了:

    若sem==0,不是睡眠,而是返回一个错误eagain。       

int sem_post(sem_t *sem);

    把指定的信号量sem的值加1;

    呼醒正在等待该信号量的任意线程。

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

    取回信号量sem的当前值,把该值保存到sval中。

    若有1个或更多的线程或进程调用sem_wait阻塞在该信号量上,该函数返回两种值:

        1) 返回0

        2) 返回阻塞在该信号量上的进程或线程数目

    linux采用返回的第一种策略。

注意:在这些函数中,只有sem_post是信号安全的函数,它是可重入函数。

1.2 接口使用的一般流程

sem_init(&sem);

sem_wait(&sem);

critical area;

sem_post(&sem);

remainder area

2.无名信号量

    无名信号量是保存在变量类型为sem_t的内存中。

int sem_init(sem_t *sem, int pshared, unsigned int value);

    1)pshared==0 用于同一多线程的同步;

    2)若pshared>0 用于多个进程间的同步,此时sem必须放在共享内存中。

int sem_destroy(sem_t *sem);

    只能销毁由sem_init初始化的信号量,否则后果不可预料也。

例1:

    多线程使用信号量的简单例子:

说明:该例子来自于usp。

    可以把sem_wait和sme_post调用去掉,看看效果,可以看到出现了交叉输出的情况。

    nanosleep调用只是为了让输出的效果更明显,没有其他意义。

更多的例子见mypxsem/prodcons2-4.c

3. 有名信号量

有名信号量是把信号量的值保存在文件中,所以它可以用于线程也可以用于进程间的同步。

如下面的形式:

3.1 常用函数说明

sem_t *sem_open(const char *name, int oflag,

                mode_t mode, unsigned int value);

    返回一个sem_t类型的指针。该指针随后可用作sem_close等的参数。

    该函数参数的详细信息,可以参考手册。

int sem_close(sem_t *sem);

    关闭sem信号量,并释放资源。   

int sem_unlink(const char *name);

    在所有进程关闭信号量后删除name的信号量

3.2 有名信号量的使用

例子:

以上代码创建了一个进程链,若把sem_wait和sem_post调用去掉,可以看到输出很混乱。

这是由于每个子进程都共享了父进程的文件表项,而且都指向打开的文件表项。

system v 信号量

===============

1, 该类信号量,与posix信号量不同。它表示的信号量集,而不是单个信号量。

可用于不同进程间的同步。

内核为每个信号量集,维护一个如下的信息结构:<sys/sem.h>

2, 信号量操作函数

a. 创建和打开信号量

int semget(key_t key, int nsems, int oflag)

(1) nsems>0  : 创建一个信的信号量集,指定集合中信号量的数量,一旦创建就不能更改。

(2) nsems==0 : 访问一个已存在的集合

(3) 返回的是一个称为信号量标识符的整数,semop和semctl函数将使用它。

(4) 创建成功后一下结构被设置:

    .sem_perm 的uid和gid成员被设置成的调用进程的有效用户id和有效组id

    .oflag 参数中的读写权限位存入sem_perm.mode

    .sem_otime 被置为0,sem_ctime被设置为当前时间

    .sem_nsems 被置为nsems参数的值

    .而于该集合中的每个信号量不初始化,这些结构是在semctl,用参数set_val,setall初始化的。

b. 设置信号量的值

int semop(int semid, struct sembuf *opsptr, size_t nops);

(1) semid 是semget返回的semid

(2) nops : 是数组opsptr的个数

(3) opsptr : 是操作结构的数组

(4) 若sem_op 是正数,其值就加到semval上;

    若sem_op 是0,那么调用者希望等到semval变为0,如果semval是0就反回;

    若sem_op 是负数,那么调用者希望等待semval变为大于或等于sem_op的绝对值.

(5) sem_flg

    sem_undo     由进程自动释放信号量

    ipc_nowait  不阻塞

c. 对信号量集实行控制操作

int semctl(int semid, int semnum, int cmd, ../* union semun arg */);

其中semid是信号量集合,semnum是信号在集合中的序号,

cmd是控制命令,参数可选

cmd取值如下:

getval, setval : semid集合中semnum信号量当前的semval值

getall,setall :semid集合中所有信号量的值。

ipc_rmid:删除semid信号量集

getpid:返回最后成功操作该信号的进程号。

ipc_stat:返回semid集合中的struct semid_ds结构。

例子: