转载地址:http://www.cnblogs.com/Anker/archive/2013/01/13/2858765.html
1、概述
信号量(semaphore)是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语。信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒。Posix信号量分为有名信号量和无名信号量(也叫基于内存的信号量)。2、Posix有名信号量
有名信号量既可以用于线程间的同步也可以用于进程间的同步。
(1)由sem_open来创建一个新的信号量或打开一个已存在的信号量。其格式为:
sem_t *sem_open(const char *name,int oflag,mode_t mode,unsigned int value);
返回:若成功则为指向信号量的指针,若出错则为SEM_FAILED 其中,第三、四个参数可以没有,主要看第二个参数如何选取。
oflag参数:可以是0、O_CREAT或O_CREAT|O_EXCL。如果指定O_CREAT标志而没有指定O_EXCL,那么只有当所需的信号量尚未存在时才初始化它。但是如果所需的信号量已经存在也不会出错。 但是如果在所需的信号量存在的情况下指定O_CREAT|O_EXCL却会报错。
mode参数:指定权限位。
value参数:指定信号量的初始值。该初始值不能超过SEM_VALUE_MAX(这个常值必须至少为32767).二值信号量的初始值通常为1,计数信号量的初始值则往往大于1。
用sem_close来关闭该信号量。
创建一个新的信号量程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <errno.h>
#include <fcntl.h>
//创建模式权限
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
int main(int argc,char *argv[])
{
int c, flags;
sem_t *sem;
unsigned int value;
flags = O_RDWR | O_CREAT;
value = 1; //初始化信号量的值为1,即二元信号量
while((c = getopt(argc,argv,"ei:"))!= -1)
{
switch(c)
{
case 'e':
flags |= O_EXCL;
break;
case 'i':
value = atoi(optarg); //获取信号量的值
break;
}
}
if(optind != argc -1)
{
printf("usage: semcreate [-e] [-i initalvalue] <name>");
exit(0);
}
//创建信号量,返回sem_t类型指针
if((sem = sem_open(argv[optind],flags,FILE_MODE,value)) == SEM_FAILED)
{
perror("sem_open() error");
exit(-1);
}
//关闭打开的信号量
sem_close(sem);
exit(0);
}
(2)使用sem_unlink删除信号量:
int sem_unlink(const char *name); 返回:成功返回0,出错返回-1
(3)获取信号量的当前值:
int sem_getvalue(sem_t *sem,int *valp); 返回:成功返回0,出错返回-1
sem_getvalue在由valp指向的整数中返回所指定信号量的当前值。如果信号量当前已上锁,那么返回值或为0,或为某个负数,绝对值即为等待等待该信号量解锁的线程数。
(4)信号量的等待:(P操作,也称为递减down 或 上锁lock)
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
返回:成功返回0,出错返回-1
sem_wait函数测试所指定信号量的值,如果该值大于0,就将它的值减1并立即返回;如果该值等于0,调用线程就被投入睡眠中,直到该值变为大于0,这时再将它减1,函数随后返回。“测试并减1”操作必须是原子的。sem_wait和sem_trywait的差别是:当所指定信号量的值已经是0时,后者并不将调用的进程投入睡眠。相反,它返回一个EAGAIN错误。如果被某个信号中断,sem_wait就可能过早的返回,返回的错误为EINTR。
(5)信号量挂出(V操作,也称为递增up 或解锁unlock)
int sem_post(sem_t *sem);返回:成功返回0,出错返回-1 将所指定的信号量值加1
3、采用有名Posix信号量实现生产者-消费者
include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <errno.h>
#include <fcntl.h>
//文件模式
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#define NBUFF 10 //槽位的个数
#define SEM_MUTEX "mutex1"
#define SEM_NEMPTY "nemtpy1"
#define SEM_NSTORED "nstored1"
int nitems; //条目的个数
//缓冲区结构
struct
{
int buff[NBUFF];
sem_t *mutex,*nempty,*nstored; //信号量
}shared;
char *px_ipc_name(const char *name);
void *produce(void *arg);
void *consume(void *arg);
int main(int argc,char *argv[])
{
pthread_t tid_produce,tid_consume;
if(argc != 2)
{
printf("usage: prodcons <#itmes>");
exit(0);
}
nitems = atoi(argv[1]); //获取条目数目
//创建二元信号量
if((shared.mutex = sem_open(SEM_MUTEX,O_CREAT,FILE_MODE,1)) == SEM_FAILED)
{
perror("sem_open() error");
exit(-1);
}
//创建nempty信号量
if((shared.nempty = sem_open(SEM_NEMPTY,O_CREAT,FILE_MODE,NBUFF)) == SEM_FAILED)
{
perror("sem_open() error");
exit(-1);
}
//创建nstored信号量
if((shared.nstored = sem_open(SEM_NSTORED,O_CREAT,FILE_MODE,0)) == SEM_FAILED)
{
perror("sem_open() error");
exit(-1);
}
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_unlink(SEM_MUTEX);
sem_unlink(SEM_NEMPTY);
sem_unlink(SEM_NSTORED);
exit(0);
}
void *produce(void *arg)
{
int i;
printf("produce is called.\n");
for(i=0;i<nitems;i++)
{
//判断是否有空槽,有的将其减少1
sem_wait(shared.nempty);
//锁住槽位,对于多个生产者的时候有必要,单个生产者没有必要
sem_wait(shared.mutex);
printf("produced a new item.\n");
shared.buff[i%NBUFF] = i;
sem_post(shared.mutex); //释放锁
sem_post(shared.nstored); //缓冲区中条目数加1
}
return NULL;
}
void *consume(void *arg)
{
int i;
printf("consumer is called.\n");
for(i=0;i<nitems;i++)
{
//判断缓冲区中是否有条目,有的话将条目数减少1
sem_wait(shared.nstored);
//锁住缓冲区,对多个消费者有必要,对单个消费者没必要
sem_wait(shared.mutex);
if(shared.buff[i % NBUFF] != i)
printf("buff[%d] = %d\n",i,shared.buff[i % NBUFF]);
printf("removed a item.\n");
sem_post(shared.mutex); //释放锁
sem_post(shared.nempty); //将缓冲区中的空槽数目加1
}
return NULL;
}
4、Posix基于内存的信号量
Posix有名信号量创建时候是用一个name参数标识,它通常指代文件系统中的某个文件。而基于内存的信号量是由应用程序分配信号量的内存空间,即分配一个sem_t数据类型的内存空间,然后由系统初始化它们的值。操作函数如下:
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value); //初始化内存信号量
int sem_destroy(sem_t *sem); //摧毁信号量
如果shared=0,那么待初始化的信号量是在同一进程的各个线程间共享的,否则该信号量是在进程间共享的,此时该信号量必须存放在某种类型的共享内存区中,使得用它的进程能够访问该共享内存区。value是该信号量的初始值。
采用基于内存的信号量实现生产者-消费者问题:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <errno.h>
#define NBUFF 10
int nitems;
//缓冲区结构
struct
{
int buff[NBUFF];
sem_t mutex,nempty,nstored;
}shared;
void *produce(void *arg);
void *consume(void *arg);
int main(int argc,char *argv[])
{
pthread_t tid_produce,tid_consume;
if(argc != 2)
{
printf("usage: prodcons <#itmes>");
exit(0);
}
nitems = atoi(argv[1]);
//创建基于内存的信号量
if(sem_init(&shared.mutex,0,1) == -1)
{
perror("sem_open() error");
exit(-1);
}
if(sem_init(&shared.nempty,0,NBUFF) == -1)
{
perror("sem_open() error");
exit(-1);
}
if(sem_init(&shared.nstored,0,0) == -1)
{
perror("sem_open() error");
exit(-1);
}
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;
printf("produce is called.\n");
for(i=0;i<nitems;i++)
{
sem_wait(&shared.nempty);
sem_wait(&shared.mutex);
printf("produced a new item.\n");
shared.buff[i%NBUFF] = i;
sem_post(&shared.mutex);
sem_post(&shared.nstored);
}
return NULL;
}
void *consume(void *arg)
{
int i;
printf("consumer is called.\n");
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]);
printf("removed a item.\n");
sem_post(&shared.mutex);
sem_post(&shared.nempty);
}
return NULL;
}