信号量的的作用,互斥和同步。
互斥就是确保对共享变量的互斥访问,基本思想就是:将每个共享变量与一个信号量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我们可以很容易改成类的形式,可以把缓冲区数据类型拓展成其他数据类型。