在前面博客linux互斥锁和PV原语, linux信号量—互斥与同步
已经讲了PV原语、互斥锁、信号量,这一节讲他们的综合使用。用一个大家经常用的生产者和消费者的例子来讲解。
/* product_consumer.c*/
#include
#include
#include
#include
#include
#include
sem_t mutex,full_readable,empty_writable ;
static int count = 0 ;
#define FIFO "myfifo"
/*线程一*/
void thread1(void * arg)
{
char write_buf[] = "1";
int fd = *(int *)arg;
printf("this is pthread1,fd = %d\n",fd);
int i = 0;
while(1)
{
/*P 操作信号量 empty_writable 和 mutex*/
sem_wait(&empty_writable);
sem_wait(&mutex);
if (write(fd,write_buf,1))
{
perror("write");
}
count++;
printf("This is a pthread1....and count = %d ,write 1 to the FIFO\n",count);
/*V 操作信号量 full_readable 和 mutex*/
sem_post(&full_readable);
sem_post(&mutex);
//sleep(1);
}
}
/*线程二*/
void thread2(void * arg)
{
char read_buf[6];
int fd = *(int *)arg;
printf("this is pthread2 and fd = %d\n",fd);
int i = 0;
int ret;
while(1)
{
/*P 操作信号量 full 和 mutex*/
sem_wait(&full_readable);
sem_wait(&mutex);
ret = read(fd,read_buf,1);
if(ret < 0)
{
perror("read");
}
else if(ret > 0)
{
count++;
printf("This is a pthread2.... count = %d, read %s from FIFO\n",count,read_buf);
}
/*V 操作信号量 avail 和 mutex*/
sem_post(&empty_writable);
sem_post(&mutex);
sleep(3);
}
}
int main(void)
{
/*创建有名管道*/
//if(mkfifo(FIFO,O_CREAT|O_EXCL)<0)
// printf("cannot create fifoserver\n");
if(mkfifo(FIFO,O_CREAT|O_EXCL|777)<0)
perror("cannot create fifoserver\n");
/*打开管道*/
int fd=open(FIFO,O_RDWR|O_NONBLOCK,0);
if(fd==-1)
{
perror("open");
}
int i,ret;
/*初始化互斥信号量为 1*/
ret=sem_init(&mutex,0,1);
/*初始化 empty_writable 信号量为 5,限制可写长度为5*/
ret=sem_init(&empty_writable,0,5);
/*初始化 full_readable 信号量为 0*/
ret=sem_init(&full_readable,0,0);
if(ret!=0)
{
perror("sem_init");
}
pthread_t id1,id2;
/*创建线程一*/
ret=pthread_create(&id1,NULL,(void *) thread1,&fd);
if(ret!=0){
perror("Create pthread error!\n");
}
/*创建线程二*/
ret=pthread_create(&id2,NULL,(void *) thread2,&fd);
if(ret!=0){
perror ("Create pthread error!\n");
}
/*等待线程结束*/
pthread_join(id1,NULL);
pthread_join(id2,NULL);
return 0;
}
实验结果:
ubuntu:~/test/pthread_test$ gcc product_consumer.c -o product_consumer -lpthread
ubuntu:~/test/pthread_test$ ./product_consumer
this is pthread1,fd = 3
this is pthread2 and fd = 3
write: Success
This is a pthread1....and count = 1 ,write 1 to the FIFO
write: Success
This is a pthread1....and count = 2 ,write 1 to the FIFO
write: Success
This is a pthread1....and count = 3 ,write 1 to the FIFO
write: Success
This is a pthread1....and count = 4 ,write 1 to the FIFO
write: Success
This is a pthread1....and count = 5 ,write 1 to the FIFO
This is a pthread2.... count = 6, read 1 from FIFO
write: Success
This is a pthread1....and count = 7 ,write 1 to the FIFO
This is a pthread2.... count = 8, read 1 from FIFO
以上程序,我们用PV原语来理解。
一开始empty_writable初始化5,表示管道有5个空位可以写。
一开始full_readable初始化为0,表示管道有0个有写的数据可读。
当线程一写了两个空位的时候,empty_writable变成了3,表示还有3个空位可写。而full_readable变成了2,表示有两个位置有数据可读。我们很容易想到下面的同步操作模式。
/*线程一*/
void thread1(void * arg)
{
sem_wait(&empty_writable);
//write 操作
sem_post(&full_readable);
}
/*线程二*/
void thread2(void * arg)
{
sem_wait(&full_readable);
//read 操作
sem_post(&empty_writable);
}
但是,这里有一个问题,就是当empty_writable为3,full_readable为2的时候,线程一和线程二都可以访问同一个管道,这时候就容易出现问题了。所以,我们这里还需要加上一把互斥锁,用于防止进程共享的资源被同时访问。
/*线程一*/
void thread1(void * arg)
{
sem_wait(&empty_writable);
sem_wait(&mutex);
//write 管道
sem_post(&full_readable);
sem_post(&mutex);
}
/*线程二*/
void thread2(void * arg)
{
sem_wait(&full_readable);
sem_wait(&mutex);
//read 管道
sem_post(&empty_writable);
sem_post(&mutex);
}
程序的功能很简单,首先在main函数中调用mkfifo(FIFO,O_CREAT|O_EXCL|777)创建管道,调用open函数打开管道;接着再创建线程一和线程二,在线程一中对管道写入数据,在线程二中读取管道的数据。从上面的试验结果可知道,线程一对管道写入5次数据之后,就无法再写入了,这是因为我们把信号量empty_writable 初始化为 5。所以,线程一开始阻塞在sem_wait(&empty_writable),然后线程二开始读取数据,读取一次,就空一个位置出来,线程一就可以再写入一次数据。