天天看点

5.线程与并发同步-生产者消费者信号量模型

进化版的互斥锁(1 --> N)

由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却无形中导致线程的并发性下降。线程从并行执行,变成了串行执行。与直接使用单进程无异。 信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。

【练习】:使用信号量完成线程间同步,模拟生产者,消费者问题。 【sem_product_consumer.c】

分析:

规定: 如果□中有数据,生产者不能生产,只能阻塞。

如果□中没有数据,消费者不能消费,只能等待数据。

定义两个信号量:S满 = 0, S空 = 1 (S满代表满格的信号量,S空表示空格的信号量,程序起始,格子一定为空)

所以有: T生产者主函数 { T消费者主函数 {

sem_wait(S空); sem_wait(S满);

生产… 消费…

sem_post(S满); sem_post(S空);

} }

假设: 线程到达的顺序是:T生、T生、T消。

那么: T生1 到达,将S空-1,生产,将S满+1

T生2 到达,S空已经为0, 阻塞

T消 到达,将S满-1,消费,将S空+1

三个线程到达的顺序是:T生1、T生2、T消。而执行的顺序是T生1、T消、T生2

这里,S空 表示空格子的总数,代表可占用信号量的线程总数–>1。其实这样的话,信号量就等同于互斥锁。

但,如果S空=2、3、4……就不一样了,该信号量同时可以由多个线程占用,不再是互斥的形式。因此我们说信号量是互斥锁的加强版。

sem_wait函数

给信号量加锁 – 操作,如果为0,则阻塞

int sem_wait(sem_t *sem);

sem_post函数

给信号量解锁 ++ 操作

int sem_post(sem_t *sem);

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>
#include<stdlib.h>

int queue[5];
sem_t black,product;//

void *producer(void*arg){
     int i=0;
	 while(1){
	 	sem_wait(&black);
		queue[i]=rand()%100+1;
		printf("produce %d\n",queue[i]);
		sem_post(&product);
		
		i=(i+1)%5;
                
		sleep(rand()%2);
	 }
}

void *conusm(void*arg){
    int i=0;
	while(1){
		sem_wait(&product);
		printf("conusm %d\n",queue[i]);
		queue[i]=0;
                sem_post(&black);
		i=(i+1)%5;
		sleep(rand()%2);
	}
}

int main(){
	pthread_t p1,p2;
	
	sem_init(&black,0,5);
	sem_init(&product,0,0);
	
	pthread_create(&p1,NULL,producer,NULL);
	pthread_create(&p2,NULL,conusm,NULL);
	
	pthread_join(p1,NULL);
	pthread_join(p2,NULL);
	
	sem_destroy(&black);
	sem_destroy(&product);
	
	return 0;
}
           

@ubuntu:~/Desktop/work/0912$ gcc sem.c -pthread

@ubuntu:~/Desktop/work/0912$ ./a.out

produce 84

conusm 84

produce 16

conusm 16

produce 87

produce 50

conusm 87

produce 28

conusm 50

produce 64

produce 41

produce 73

produce 12

conusm 28

produce 30

conusm 64

produce 63

conusm 41

^C

【推演练习】: 理解上述模型,推演,如果是两个消费者,一个生产者,是怎么样的情况。

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>
#include<stdlib.h>
#include<queue>

using namespace std;

queue<int>my_Queue;

sem_t black,product;//

void *producer(void*arg){
    
	 while(1){
	 	sem_wait(&black);
		int num= rand() % 1000 + 1;        //模拟生产一个产品
		my_Queue.push (num);	//产品入队
		
		printf("---produce %d, now total = %lu\n",num,my_Queue.size());
		sem_post(&product);
		
		usleep(50);
	 }
}

void *conusm(void*arg){
 
        srand(time(NULL));
	while(1){
		sem_wait(&product);
		int num=my_Queue.front(); //获取队头元素
		my_Queue.pop();//出
		printf("id: %lu, conusm %d, now total = %lu\n",pthread_self(),num,my_Queue.size());
		sem_post(&black);
	
	
		sleep(rand()%2+1);
	}
}

int main(){
	pthread_t p1,p2[2];
	
	sem_init(&black,0,5);
	sem_init(&product,0,0);
	
	pthread_create(&p1,NULL,producer,NULL);

	pthread_create(&p2[0],NULL,conusm,NULL);
	pthread_create(&p2[1],NULL,conusm,NULL);
	
	pthread_join(p1,NULL);
	pthread_join(p2[0],NULL);
	pthread_join(p2[1],NULL);
	
	sem_destroy(&black);
	sem_destroy(&product);
	
	return 0;
}
           

—produce 911, now total = 1

id: 140211254994688, conusm 911, now total = 0

—produce 21, now total = 1

id: 140211263387392, conusm 21, now total = 0

—produce 27, now total = 1

—produce 443, now total = 2

—produce 781, now total = 3

—produce 665, now total = 4

—produce 542, now total = 5

id: 140211263387392, conusm 27, now total = 4

—produce 425, now total = 5

id: 140211254994688, conusm 443, now total = 4

—produce 499, now total = 5

id: 140211254994688, conusm 781, now total = 4

id: 140211263387392, conusm 665, now total = 3

—produce 96, now total = 4

—produce 914, now total = 5

id: 140211254994688, conusm 542, now total = 4

id: 140211263387392, conusm 425, now total = 3

—produce 317, now total = 4

—produce 645, now total = 5

继续阅读