利用信号量来调度共享资源 生产者-消费者问题

来源:互联网 发布:python 支持向量机 编辑:程序博客网 时间:2024/05/17 05:00

            信号量的的作用,互斥和同步。

        互斥就是确保对共享变量的互斥访问,基本思想就是:将每个共享变量与一个信号量s(初始化为1)联系起来,然后用P(s)和V(s)操作将相应的临界区包围起来。

        同步就是调度对共享资源的访问。信号量的同步:经典问题是生产者-消费者问题。

        生产者-消费者的问题描述如下:

  

         在本节中,我们将开发一个简单的包,叫做SBUF,用来构造生产者-消费者程序。在下一节中,我们会看到如何用它来构造一个基于预线程化的并发服务器。

         SBUF操作类型为sbuf_t的有限缓冲区,项目存放在一个动态分配的n項整数数组buf中。front 和 rear 索引值记录该数组的第一项和最后一项。三个信号量同步对缓冲区的访问mutex信号量提供互斥的缓冲区访问。slots和items信号量分别记录空槽位和可用项目的数量。

        提示:下面的代码是在linux下运行的,在window下运行不了,需要链接线程库 pthread,代码是从从深入理解计算机系统中摘录下来的。

        注:csapp.h 是包装函数,包含了各种头文件和相关函数的错误处理,等一下会说明。

sbuf_h文件:         

<span style="color:#ff0000;">#ifndef __SBUF_H__#define __SBUF_H__</span>#include "csapp.h"/* $begin sbuft */typedef struct {    int *buf;          /* Buffer array */             int n;             /* Maximum number of slots */    int front;         /* buf[(front+1)%n] is first item */    int rear;          /* buf[rear%n] is last item */    <span style="color:#ff0000;">sem_t mutex;       /* Protects accesses to buf */</span>    <span style="color:#ff0000;">sem_t slots;       /* Counts available slots */</span>    <span style="color:#ff0000;">sem_t items;       /* Counts available items */</span>} sbuf_t;/* $end sbuft */void sbuf_init(sbuf_t *sp, int n);void sbuf_deinit(sbuf_t *sp);void sbuf_insert(sbuf_t *sp, int item);int sbuf_remove(sbuf_t *sp);<span style="color:#ff0000;">#endif</span> /* __SBUF_H__ */

注释: 

           1、学习头文件的定义方式,使用#ifndef

         2、sem_t 信号量定义在文件semaphore.h

         3、三个信号量同步对缓冲区的访问mutex信号量提供互斥的缓冲区访问。slots和items信号量分别记录空槽位和可用项目的数量。

         4、mutex:缓冲区的读取和写入不能同时进行,mutex保证对buffer的访问是互斥的

       现在给出 SBUF函数的实现,sbuf_init函数为缓冲区分配堆存储器,设置front 和 rear表示一个空的缓冲区,并为三个信号赋初始值。sbuf_deini是在应用程序使用完缓冲区时,释放缓冲区存储的。sbuf_insert函数等待一个可用的槽位,对互斥锁加锁,添加项目,对互斥锁解锁,然后宣布一个新项目可用。sbuf_remove在等待一个可用的缓冲区项目后,对互斥锁加锁,从缓冲区的前面取出该项目,对互斥锁解锁,然后发信号通知一个新的槽位可用。

/* $begin sbufc */#include "csapp.h"#include "sbuf.h"/* Create an empty, bounded, shared FIFO buffer with n slots *//* $begin sbuf_init */void sbuf_init(sbuf_t *sp, int n){    sp->buf = Calloc(n, sizeof(int));     sp->n = n;                       /* Buffer holds max of n items */    <span style="color:#ff6666;">sp->front = sp->rear = 0;        /* Empty buffer iff front == rear */</span><span style="color:#ff0000;">    Sem_init(&sp->mutex, 0, 1);      /* Binary semaphore for locking */    Sem_init(&sp->slots, 0, n);      /* Initially, buf has n empty slots */    Sem_init(&sp->items, 0, 0);      /* Initially, buf has zero data items */</span>}/* $end sbuf_init *//* Clean up buffer sp *//* $begin sbuf_deinit */void sbuf_deinit(sbuf_t *sp){    Free(sp->buf);}/* $end sbuf_deinit *//* Insert item onto the rear of shared buffer sp *//* $begin sbuf_insert */void sbuf_insert(sbuf_t *sp, int item){    <span style="color:#ff0000;">P(&sp->slots);   </span>                       /* Wait for available slot */    <span style="color:#3333ff;">P(&sp->mutex);</span>                          /* Lock the buffer */    sp->buf[(++sp->rear)%(sp->n)] = item;   /* Insert the item */   <span style="color:#3366ff;"> V(&sp->mutex);</span>                          /* Unlock the buffer */    V(&sp->items);                          /* Announce available item */}/* $end sbuf_insert *//* Remove and return the first item from buffer sp *//* $begin sbuf_remove */int sbuf_remove(sbuf_t *sp){    int item;    P(&sp->items);                          /* Wait for available item */    P(&sp->mutex);                          /* Lock the buffer */    item = sp->buf[(++sp->front)%(sp->n)];  /* Remove the item */    V(&sp->mutex);                          /* Unlock the buffer */    <span style="color:#ff0000;">V(&sp->slots);</span>                          /* Announce available slot */    return item;}/* $end sbuf_remove *//* $end sbufc */

注释:

1、  Sem_init(&sp->mutex, 0, 1); -互斥锁初始化为1 

       Sem_init(&sp->slots, 0, n);   -初始化可用锁为n

       Sem_init(&sp->items, 0, 0); -初始化可用项目为0

2、互斥问题,P,V操作会在同一个进程(函数)中出现(代码中,标蓝的部分)

      同步问题,P,V操作会在不同的进程(函数)中出现(代码中, 标红的部分)

3、P(&sp->slots);P(&sp->mutex);这两个P操作不能对调,因为,若一个缓冲区已满,P(&sp->mutex),生产者占用了缓冲区,之后执行P(&sp->slots),因为缓冲区已满,生产者挂起,因为生产者占用了缓冲区,所以,消费者就无法进入缓冲区,导致死锁。

4、V(&sp->mutex);V(&sp->items);是可以对调的,逻辑上不会出错,但颠倒之后临界区会扩大,不合适。

5、P操作可以是调用者进程挂起,但V操作不会使调用者进程挂起


接下来:介绍上面使用过的几个函数,都是包装完了,包含了错误处理,详情见博客:http://blog.csdn.net/lujiandong1/article/details/45486063

strerror(errno):获取errno对应的错误

errno 是全局错误整数变量,由系统定义

下面的几个函数都是在 csapp.c中定义的

/**************************  * Error-handling functions **************************//* $begin errorfuns *//* $begin unixerror */  void unix_error(char *msg) /* unix-style error */{    fprintf(stderr, "%s: %s\n", msg, strerror(errno));    exit(0);}/* $end unixerror */

void *Calloc(size_t nmemb, size_t size) {    void *p;    if ((p = calloc(nmemb, size)) == NULL)unix_error("Calloc error");    return p;}

void Free(void *ptr) {    free(ptr);}

void Sem_init(sem_t *sem, int pshared, unsigned int value) {    if (sem_init(sem, pshared, value) < 0)unix_error("Sem_init error");}

void P(sem_t *sem) {    if (sem_wait(sem) < 0)unix_error("P error");}

void V(sem_t *sem) {    if (sem_post(sem) < 0)unix_error("V error");}


总结:1、使用错误包装处理函数

           2、SBUF我们可以很容易改成类的形式,可以把缓冲区数据类型拓展成其他数据类型。


     

0 0
原创粉丝点击