PV操作与信号量(改编)
信号量(semaphore)是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语。珂分为三种类型的信号量。
:也叫做有名信号量,使用Posix IPC名字标识,可用于进程或线程间的同步。
:也叫做无名信号量,存放在共享主存中,可用于进程或线程间的同步。
:也是有名信号量,在内核中维护,可用于进程或线程间的同步。
其中,Posix信号量不必在内核中维护,并且是由可能与文件系统路径名来标识的,但是,其并不真正存放在文件系统的某个文件中。
信号量可以为最简单的二值信号量,同时也可以为任意一个非负的值,二值信号量的一个用途就是可以用来实现互斥锁。
对于有名信号量(named),其用到函数如下:
sem_open();
sem_wait();
sem_trywait();
sem_post();
sem_getvalue();
sem_close();
sem_unlink();
对于无名信号量(unamed 或者 memory_based)
sem_init();
sem_wait();
sem_trywait();
sem_post();
sem_getvalue();
sem_destroy();
以上函数包含在
头文件中。
sem_open
创建一个新的有名信号量或打开一个已经存在的有名信号量。
sem_t *sem_open(const char *name,int flag,.../*mode_t mode,unsigned int value*/)
返回值:成功返回一个sem_t类型的指针,失败返回SEM_FAILED (#define SEM_FAILED ((sem_t*)(-1)))
flag参数可以是0,O_CREAT或O_CREAT|OEXCL(两者同时指定,表明只在该信号量不存在时才指定,否者返回一个EEXIST错误,若只指定O_CREAT,那么信号量已经存在的条件下不会报错,其含义是,若信号量尚未存在,那就创建并初始化它),这些值通常定义在
头文件中, 如果指定了是O_CREAT标志,那么第三个和第四个参数是必要的,其中mode参数指定权限位,value参数指定信号量的初始值,该初始值不能超过SEM_VALUE_MAX(这个初始值必须至少为32767)。可用的权限位大致有:
0664 | read-write by owner and group, read by others |
---|---|
0666 | read-write by everyone |
0444 | read-only by everyone |
0644 | read-write by owner, read by everyone else |
0 | don't set the protection mode (skips setting) |
sem_close
使用
打开的有名信号量,使用
将其关闭。
int sem_close(sem_t *sem);
返回值:成功返回0,失败返回-1。
一个进程终止时,内核将所有任然打开的有名信号量制动执行这个操作,关闭一个信号量并没有将它从系统中删除,有名信号使用
从系统中删除。
sem_unlink
int sem_unlink(const char *name);
返回值:若成功返回0,出错返回-1。
sem_wait和sem_trywait (对应于PV操作中的P操作)
函数测试所指定的信号量,若该值大于1,那么就将它减一并立即返回。若该值等于0,调用线程就被投入睡眠中,直到该值变为大于0,这时再将它减一。
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
返回值:若成功则为0,若出错则为-1。
对于
函数来说,若其信号量的值已经是0,那么其不陷入睡眠,而是抛出一个EAGAIN错误。如果某个信号中断,
函数可能就过早地返回,所返回地错误为EINTR。
sem_post (对应于PV操作中的V操作) 和sem_getvalue
用完某个信号量后调用
将信号量的值加一。
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem,int *valp);
返回值:成功返回0,失败返回-1。
在由valp指向的整数中返回所指定信号量当前的值。若当前信号量已上锁,返回值为0,若为某个负数,其绝对值就是等待该信号量解锁的线程数。下面是一个使用
的例程。
#include <stdio.h>
#include <semaphore.h>
int main(int argc,char **argv){
sem_t *sem;
int val;
sem = sem_open(argv[1],0);//argv[1]用来指定信号量的名字。
sem_getvalue(sem,&val);
printf("the value of sem is %d\n",val);
return 0;
}
PV操作有一个经常使用的同步模型,就是生产者,消费者模型,简单来说,就是货柜数量有限,比如说,生产者生产产品,消费者消费产品,但是,当消费者没来得及消费产品,而生产者持续生产以至于货柜放满后,生产者必须等待消费者消费后才能继续生产,同样对于消费者,若商品数不够的话,那么其也不能继续消费。
对于这个模型,我们可以设置十个货柜。
#define NBUFF 10
#define SEM_NEMPTY "nempty"
#define SEM_NSTORED "nstored"
struct {
int buff[NBUFF];//货柜数组
sem_t *mutex, *nempty, *nstored; //mutex为一个二值信号量,用来设置对货柜操作的互斥。
}shared;
shared.nempty = sem_open(SEM_NEMPTY,O_CREAT, 0644, NBUFF);
shared.nstored = sem_open(SEM_NSTORED,O_CREAT, 0644,0);
这里的货柜就是一个数组,生产的货物就是每个数字,这个数字用来验证消费者消费的产品是否为生产者生产的相应产品。
信号量是消费者用来判断是否还有存货的信号量,
是生产者判断货柜是否填满的信号量。在定义生产者消费者线程之前首先需要确认一个对货柜操作的互斥锁,这个锁可以用一个二值信号量实现。
#define SEM_NEMPTY "nempty"
shared.mutex = sem_open(SEM_MUTEX, O_CREAT, 0644, 1);
生产者线程
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;
std::cout<<"produce buff["<<i%NBUFF<<"], the value of which is "<<i<<std::endl;
sem_post(shared.mutex);
sem_post(shared.nstored);
}
return 0;
}
消费者线程
void *consume(void *arg){
int i;
for(i = 0;i<nitems;i++){
sem_wait(shared.nstored);
sem_wait(shared.mutex);
std::cout<<"consume buff["<<i<<"] = "<<shared.buff[i % NBUFF]<<std::endl;
sem_post(shared.mutex);
sem_post(shared.nempty);
}
return 0;
}
主函数
#include <iostream>
#include <sys/types.h>
#include <fcntl.h>
#include <semaphore.h>
#incldue <thread>
int nitems;
int main(int argc,char **argv){
pthread_t tid_produce, tid_consume;
nitems = 20;
shared.mutex = sem_open(SEM_MUTEX, O_CREAT, 0644, 1);
shared.nempty = sem_open(SEM_NEMPTY,O_CREAT, 0644, NBUFF);
shared.nstored = sem_open(SEM_NSTORED,O_CREAT, 0644,0);
pthread_setconcurrency(2);//这个用来告诉操作系统线程数,可有可无。
pthread_create(&tid_produce,NULL,produce,NULL);//第一个NULL用来指定线程的属性
pthread_create(&tid_consume,NULL,consume,NULL);//第二个NULL用来指定线程的参数
pthread_join(tid_produce,NULL);
pthread_join(tid_consume,NULL);
sem_unlink(SEM_MUTEX);
sem_unlink(SEM_NEMPTY);
sem_unlink(SEM_NSTORED);
return 0;
}