信号量(semaphore)是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语。信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒。Posix信号量分为有名信号量和无名信号量(也叫基于内存的信号量)。
1、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来关闭该信号量。
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,或为某个负数,绝对值即为等待等待该信号量解锁的线程数。
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
下面看一个例子:采用Posix信号量实现生产者-消费者问题
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#define MAX 100
#define BUFSIZE 10
int buf[BUFSIZE]={0};
int in=0;
int out=0;
sem_t full,empty;
void print(void)
{
int i;
printf("buf is:");
for(i=out;i<in;i++)
printf("%d ",buf[i]);
printf("\n");
}
void* producer(void* arg)
{
int i;
for (i = 1; i <= MAX; ++i)
{
/*produce*/
sem_wait(&full);
buf[in++]=i;
in%=BUFSIZE;
print();
sem_post(&empty);
}
pthread_exit((void*)"thread1 exit\n");
}
void* comsumer(void* arg)
{
int i;
for (i = 1; i <= MAX; ++i)
{
sem_wait(&empty);
out++;
out%=BUFSIZE;
sem_post(&full);
}
pthread_exit((void*)"thread2 exit\n");
}
int main(void)
{
void *tret;
sem_init(&full,0,10);
sem_init(&empty,0,0);
pthread_t tid_producer,tid_comsumer;
pthread_create(&tid_producer,NULL,producer,NULL);
pthread_create(&tid_comsumer,NULL,comsumer,NULL);
pthread_join(tid_producer,&tret);
printf("%s\n",(char*)tret);
pthread_join(tid_comsumer,&tret);
printf("%s\n",(char*)tret);
sem_destroy(&full);
sem_destroy(&empty);
return 0;
}
2.
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;
}
多个生产者、多个消费者的情况:
struct//缓存区结构
{
int buff[NBUFF];
int nput; //生产者产生新条目的下标
int nputval;
int nget; //消费者移除条目的下标
int ngetval;
sem_t mutex,nempty,nstored;
}shared;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <errno.h>
#define NBUFF 10
#define MAXNTHREADS 100
int nitems,nproducers,nconsumers;
struct
{
int buff[NBUFF];
int nput;
int nputval;
int nget;
int ngetval;
sem_t mutex,nempty,nstored;
}shared;
void *produce(void *arg);
void *consume(void *arg);
int main(int argc,char *argv[])
{
int i,prodcount[MAXNTHREADS],conscount[MAXNTHREADS];
pthread_t tid_produce[MAXNTHREADS],tid_consume[MAXNTHREADS];
if(argc != 4)
{
printf("usage: prodcons <#itmes> <#producers> <#consumers>");
exit(0);
}
nitems = atoi(argv[1]);
nproducers = atoi(argv[2]);
nconsumers = atoi(argv[3]);
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(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,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;
printf("produce is called.\n");
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;
}
return NULL;
}
void *consume(void *arg)
{
int i;
printf("consumer is called.\n");
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 % NBUFF]);
shared.nget++;
shared.ngetval++;
sem_post(&shared.mutex);
sem_post(&shared.nempty);
*((int*)arg) += 1;
}
return NULL;
}