《UNIX网络编程 卷2》 笔记: Posix信号量

来源:互联网 发布:最有名的网络武侠小说 编辑:程序博客网 时间:2024/06/02 05:37

信号量是另一种用于不同进程间或一个给定进程的不同线程间同步的IPC对象。对一个信号量可以执行3种操作:

    1. 创建(create)一个信号量。要求调用者指定初始值。通常初始值为0或1的称作二值信号量,初始值为N(N>=0)的称作计数信号量,N指示可用的资源数(比如说缓冲区个数)。

    2. 等待(wait)一个信号量。该操作测试信号量的值。如果它的值大于0,则将值减1;如果它的值小于或等于0,则阻塞等待其值变为大于0,然后将值减1。

    3. 挂起(post)一个信号量。该操作将信号量的值加1。

Posix信号量的数据类型是sem_t, 它有两种类型:有名信号量基于内存的信号量。Linux提供的信号量API如下:

#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);/*关闭一个有名信号量*/int sem_close(sem_t *sem);/*删除一个有名信号量*/int sem_unlink(const char *name);/*初始化一个基于内存的信号量*/int sem_init(sem_t *sem, int pshared, unsigned int value);/*销毁一个基于内存的信号量*/int sem_destroy(sem_t *sem);/*等待一个信号量*/int sem_wait(sem_t *sem);int sem_trywait(sem_t *sem);/*挂起一个信号量*/int sem_post(sem_t *sem);/*获取信号量的值*/int sem_getvalue(sem_t *sem, int *sval);

它们之间的关系如下图所示:


注意基于内存的信号量初始化函数sem_init的pshared参数是如何使用的。如果pshared参数为0,那么该信号量是在同一进程的各个线程间共享的。否则该信号量是在进程间共享的,它必须存放在某种类型的共享内存区。


二值信号量可用于互斥目的,就像互斥锁一样。下图给出了一个例子:



信号量有一个互斥锁不具备的特性:互斥锁必须总是由锁住它的线程解锁,而信号量的挂起操作却不必由执行它的等待操作的同一线程执行。信号量的这种特性可用于线程间行为同步。先来看一个在互斥锁这一节讲述的生产者-消费者问题的简单模型。



在这个模型中,只有一个生产者和消费者线程,而且它们只共享一个缓冲区。它们之间的行为被彼此约束: 生产者只有在缓冲区中的数据被消费者处理后(缓冲区为空)才能放入新的数据。消费者只有在缓冲区已被生产者放入数据后(缓冲区不为空)才能处理数据。对生产者而言,缓冲区为空表示有资源可用。对消费者而言,缓冲区不为空表示有资源可用。

为了解决这个问题,我们需要使用两个信号量get和put,因为初始缓冲区为空,所以我们把put的初始值置为1表示生产者的可用资源数为1,把get的初始值置为0表示消费者的初始可用资源数为0,程序伪代码如下图所示:



这个模型非常重要!!!

回过头来再看看我们之前讲述的一个生产者-消费者问题。


现在我们要对这个问题进行扩展,把共享缓冲区(数组)用作一个环形缓冲区,大小定为10。生产者在数组最后一项放入数据后又重新在数组第一项放入数据,消费者在验证数组最后一项数据后又从数组第一项开始验证数据。这个问题根据生产者和消费者的数量有如下三种情形。

一、单个生产者和单个消费者

这种情形下,该问题与上述共享一个缓冲区的问题很类似。我们需要两个信号量nempty和nstored,nempty初始值为10表示生产者可用资源数,nstored初始值为0表示消费者可用资源数。代码如下:

#include "unpipc.h"#define NBUFF 10int nitems;struct {int buff[NBUFF];sem_t mutex, nempty, nstored;} shared;void *produce(void *);void *consume(void *);int main(int argc, char **argv){pthread_t tid_produce, tid_consume;if (argc != 2)err_quit("usgae: prodcons1 <#items>");nitems = atoi(argv[1]);/*信号量初始化*///Sem_init(&shared.mutex, 0, 1);Sem_init(&shared.nempty, 0, NBUFF);Sem_init(&shared.nstored, 0, 0);Pthread_setconcurrency(2);Pthread_create(&tid_produce, NULL, produce, NULL);Pthread_create(&tid_consume, NULL, consume, NULL);Pthread_join(tid_produce, NULL);Pthread_join(tid_consume, NULL);//Sem_destroy(&shared.mutex);Sem_destroy(&shared.nempty);Sem_destroy(&shared.nstored);exit(0);}void* produce(void *arg){int i;for (i = 0; i < nitems; i++) {/*等待直到有空闲的缓冲区*/Sem_wait(&shared.nempty);//Sem_wait(&shared.mutex);shared.buff[i % NBUFF] = i;//Sem_post(&shared.mutex);Sem_post(&shared.nstored);}return NULL;}void* consume(void *arg){int i;for (i = 0; i < nitems; i++) {/*等待直到有缓冲区被放入数据*/Sem_wait(&shared.nstored);//Sem_wait(&shared.mutex);if (shared.buff[i % NBUFF] != i)printf("buff[%d] = %d\n", i, shared.buff[i % NBUFF]);//Sem_post(&shared.mutex);Sem_post(&shared.nempty);}return NULL;}

注意:我注释掉了书中展示的代码中二值信号量mutex的使用。因为数组下标i的值不是共享的,而且生产者和消费者不可能同时使用相同的i值,所以不需要使用互斥。

二、多个生产者和单个消费者

在有多个生产者的情形下,生产者使用的数组下标和放入的数据值都变成共享的了,需要互斥锁保护。为此代码为生产者新增了4个变量nput、nputval、mutex和nproducers。mutex是一个二值信号量,作为互斥锁使用。nproducers表示生产者线程的数量。代码如下:

#include "unpipc.h"#define NBUFF 10#define MAXNTHREADS 100int nitems, nproducers;struct {int buff[NBUFF];int nput;int nputval;sem_t mutex, nempty, nstored;} shared;void *produce(void *);void *consume(void *);int main(int argc, char **argv){int i, count[MAXNTHREADS];pthread_t tid_produce[MAXNTHREADS], tid_consume;if (argc != 3)err_quit("usgae: prodcons3 <#items> <#producers>");nitems = atoi(argv[1]);nproducers = min(atoi(argv[2]), MAXNTHREADS); /*生产者线程数量*/Sem_init(&shared.mutex, 0, 1);Sem_init(&shared.nempty, 0, NBUFF);Sem_init(&shared.nstored, 0, 0);Pthread_setconcurrency(nproducers + 1);for (i = 0; i < nproducers; i++) {count[i] = 0;Pthread_create(&tid_produce[i], NULL, produce, &count[i]);}Pthread_create(&tid_consume, NULL, consume, NULL);for (i = 0; i < nproducers; i++) {Pthread_join(tid_produce[i], NULL);printf("count[%d] = %d\n", i, count[i]);}Pthread_join(tid_consume, NULL);Sem_destroy(&shared.mutex);Sem_destroy(&shared.nempty);Sem_destroy(&shared.nstored);exit(0);}void* produce(void *arg){int i;for ( ; ; ) {Sem_wait(&shared.nempty);Sem_wait(&shared.mutex);if (shared.nput >= nitems) {Sem_post(&shared.nempty);Sem_post(&shared.mutex);return NULL;}shared.buff[shared.nput % NBUFF] = shared.nputval;shared.nput++;shared.nputval++;Sem_post(&shared.mutex);Sem_post(&shared.nstored);*((int *)arg) += 1;}}void* consume(void *arg){int i;for (i = 0; i < nitems; i++) {Sem_wait(&shared.nstored);if (shared.buff[i % NBUFF] != i)printf("buff[%d] = %d\n", i, shared.buff[i % NBUFF]);Sem_post(&shared.nempty);}return NULL;}

我们必须小心地处理生产者线程终止时的行为,在终止时它必须挂起nempty和mutex信号量。如果不执行挂起nempty信号量的操作,那么当生产者线程数N大于缓冲区个数10时,N-10个线程会永远阻塞。

三、多个生产者和多个消费者

在有多个消费者的情形下,消费者验证数据使用的数组下标和数据值都变成共享的,需要使用互斥锁保护。为了方便,我们还是只使用一个互斥锁mutex。为消费者我们新增了3个变量nget、ngetval和nconsumers。代码如下:

#include "unpipc.h"#define NBUFF 10#define MAXNTHREADS 100int nitems, nproducers, nconsumers; struct {int buff[NBUFF];int nput;int nputval;int nget;int ngetval;sem_t mutex, nempty, nstored;} shared;void *produce(void *);void *consume(void *);int main(int argc, char **argv){int i, prodcount[MAXNTHREADS], conscount[MAXNTHREADS];pthread_t tid_produce[MAXNTHREADS], tid_consume[MAXNTHREADS];if (argc != 4)err_quit("usgae: prodcons4 <#items> <#producers> <#consumers>");nitems = atoi(argv[1]);nproducers = min(atoi(argv[2]), MAXNTHREADS);nconsumers = min(atoi(argv[3]), MAXNTHREADS);Sem_init(&shared.mutex, 0, 1);Sem_init(&shared.nempty, 0, NBUFF);Sem_init(&shared.nstored, 0, 0);Pthread_setconcurrency(nproducers + nconsumers);for (i = 0; i < nproducers; i++) {prodcount[i] = 0;Pthread_create(&tid_produce[i], NULL, produce, &prodcount[i]);}for (i = 0; i < nconsumers; i++) {conscount[i] = 0;Pthread_create(&tid_consume[i], NULL, consume, &conscount[i]);}for (i = 0; i < nproducers; i++) {Pthread_join(tid_produce[i], NULL);printf("producer count[%d] = %d\n", i, prodcount[i]);}for (i = 0; i < nconsumers; i++) {Pthread_join(tid_consume[i], NULL);printf("consumer count[%d] = %d\n", i, conscount[i]);}Sem_destroy(&shared.mutex);Sem_destroy(&shared.nempty);Sem_destroy(&shared.nstored);exit(0);}void* produce(void *arg){int i;for ( ; ; ) {Sem_wait(&shared.nempty);Sem_wait(&shared.mutex);if (shared.nput >= nitems) {Sem_post(&shared.nstored);Sem_post(&shared.nempty);Sem_post(&shared.mutex);return NULL;}shared.buff[shared.nput % NBUFF] = shared.nputval;shared.nput++;shared.nputval++;Sem_post(&shared.mutex);Sem_post(&shared.nstored);*((int *)arg) += 1;}}void* consume(void *arg){int i;for ( ; ; ) {Sem_wait(&shared.nstored);Sem_wait(&shared.mutex);if (shared.nget >= nitems) {Sem_post(&shared.nstored);Sem_post(&shared.mutex);return NULL;}i = shared.nget % NBUFF;if (shared.buff[i] != shared.ngetval)printf("error: buff[%d] = %d\n", i, shared.buff[i]);shared.nget++;shared.ngetval++;Sem_post(&shared.mutex);Sem_post(&shared.nempty);*((int *)arg) += 1;}}

要注意的还是生产者线程终止时的行为,我们新增了一个挂起nstored信号量的操作。因为所有的数据都验证完时nstored值为0,所有的消费者线程都在阻塞等待nstored信号量,如果不挂起nstored信号量,那么所有的消费者线程都会永远阻塞。



阅读全文
0 0
原创粉丝点击