条件变量---生产者消费者问题

不羁的心 提交于 2019-12-04 22:05:00
  • 假设有一个生产者线程,一个消费者线程,生产一个,消费一个。我们来看看怎么实现。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int buffer;
int count = 0;

void put(int value)
{
        assert(count == 0);
        count = 1;
        buffer = value;
}

int get()
{
        assert(count == 1);
        count = 0;
        return buffer;
}

void *producer(void *arg)
{
        printf("producer...\n");
        int i;
        int loops = (int) arg;
        for (i = 0; i < loops; i++) {
                put(i);
                printf("put:%d\n",i);
        }
}

void *consumer(void *arg)
{
        printf("consumer...\n");
        int i;
        while(1) {
                int tmp = get();
                printf("%d\n",tmp);
        }
}

int main()
{
        printf("begin...\n");
        pthread_t p;
        pthread_create(&p, NULL, consumer,(void *)100);
        producer((void*)100);
        pthread_join(p, NULL);
        return 0;
}

上面的代码运行,显然不能满足我们要求,这个时候就需要条件变量。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int buffer;
int count = 0;

pthread_cond_t cond;
pthread_mutex_t mutex;

void put(int value) 
{
    assert(count == 0);
    count = 1;
    buffer = value;
}

int get()
{
    assert(count == 1);
    count = 0;
    return buffer;
}

void *producer(void *arg)
{
    printf("producer...\n");
    int i;
    int loops = (int) arg;
    for (i = 0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        if(count == 1)
            pthread_cond_wait(&cond, &mutex);
        put(i);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
        printf("put:%d\n",i);
    }
}

void *consumer(void *arg)
{
    printf("consumer...\n");
    int i;
    int loops = (int)arg;
    for(i =0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        if(count == 0)
            pthread_cond_wait(&cond, &mutex);
        int tmp = get();
        printf("%d\n",tmp);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
    }
}
[zf@localhost ch30]$ ./a.out 
begin...
producer...
put:0
consumer...
0
put:1
1
put:2
2
put:3
3
put:4
4

结果来看是满足要求的,可是如果有两个消费者呢?

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int buffer;
int count = 0;

pthread_cond_t cond;
pthread_mutex_t mutex;

void put(int value) 
{
    assert(count == 0);
    count = 1;
    buffer = value;
}

int get()
{
    assert(count == 1);
    count = 0;
    return buffer;
}

void *producer(void *arg)
{
    printf("producer...\n");
    int i;
    int loops = (int) arg;
    for (i = 0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        if(count == 1)
            pthread_cond_wait(&cond, &mutex);
        put(i);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
        printf("put: %d",i);
    }
}

void *consumer(void *arg)
{
    printf("consumer...\n");
    int i;
    int loops = (int)arg;
    for(i =0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        if(count == 0)
            pthread_cond_wait(&cond, &mutex);
        int tmp = get();
        printf("[%d]get: %d\n",pthread_self(),tmp);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    printf("begin...\n");
     pthread_t p,p1;
    pthread_create(&p, NULL, consumer,(void *)10000);
    pthread_create(&p1, NULL, consumer, (void*)10000);

    producer((void*)20000);
    pthread_join(p, NULL);
    pthread_join(p1, NULL);
    return 0;
}
put: 1207[1848375040]get: 1207
put: 1208[1839982336]get: 1208
put: 1209[1848375040]get: 1209
put: 1210[1839982336]get: 1210
put: 1211[1848375040]get: 1211
a.out: main.c:21: get: Assertion `count == 1' failed.
已放弃(吐核)

我们发现失败了,这是为什么呢?我们来思考一下,首先和if语句有关系。就是消费者Tc1被信号唤醒的时候,刚好被Tc2抢占,消耗了缓存,导致Tc1再消耗的时候会触发断言,这个问题应该怎么办呢?其实很简单,用while代替if,我们再次检查一下容量。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int buffer;
int count = 0;

pthread_cond_t cond;
pthread_mutex_t mutex;

void put(int value) 
{
    assert(count == 0);
    count = 1;
    buffer = value;
}

int get()
{
    assert(count == 1);
    count = 0;
    return buffer;
}

void *producer(void *arg)
{
    printf("producer...\n");
    int i;
    int loops = (int) arg;
    for (i = 0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        while(count == 1)
            pthread_cond_wait(&cond, &mutex);
        put(i);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
        printf("put: %d",i);
    }
}

void *consumer(void *arg)
{
    printf("consumer...\n");
    int i;
    int loops = (int)arg;
    for(i =0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        while(count == 0)
            pthread_cond_wait(&cond, &mutex);
        int tmp = get();
        printf("[%d]get: %d\n",pthread_self(),tmp);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    printf("begin...\n");
     pthread_t p,p1;
    pthread_create(&p, NULL, consumer,(void *)10000);
    pthread_create(&p1, NULL, consumer, (void*)10000);

    producer((void*)20000);
    pthread_join(p, NULL);
    pthread_join(p1, NULL);
    return 0;
}
put: 1276[1475249920]get: 1276
put: 1277[1466857216]get: 1277
put: 1278[1475249920]get: 1278
put: 1279[1466857216]get: 1279
put: 1280[1475249920]get: 1280
put: 1281[1466857216]get: 1281
put: 1282[1466857216]get: 1282
put: 1283[1475249920]get: 1283
put: 1284[1466857216]get: 1284
put: 1285[1466857216]get: 1285

可是结果并非预期的那样,发现程序执行到一半不动了,卡住了。这是为什么呢?我们加打印看一下

void *producer(void *arg)
{
    printf("producer...\n");
    int i;
    int loops = (int) arg;
    for (i = 0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        while(count == 1){
            printf("producer wait......\n");
            pthread_cond_wait(&cond, &mutex);
        }
        put(i);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
        printf("put: %d",i);
    }
}

void *consumer(void *arg)
{
    printf("consumer...\n");
    int i;
    int loops = (int)arg;
    for(i =0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        while(count == 0){
            printf("consumer wait.....\n");
            pthread_cond_wait(&cond, &mutex);
        }
        int tmp = get();
        printf("[%d]get: %d\n",pthread_self(),tmp);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
    }
}
put: 197producer wait......
[632694528]get: 197
consumer wait.....
consumer wait.....

这个发现一个问题,就是消费者T1消费后,唤醒的是T2,这样就三个线程都在休眠,这个真是个可怕的问题,我们应该怎么办呢?按道理,消费者消费后应该唤醒生产者,所以消费者和生产者应该发出不同信号,这样不就好了吗

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int buffer;
int count = 0;

pthread_cond_t empty,fill;
pthread_mutex_t mutex;

void put(int value) 
{
    assert(count == 0);
    count = 1;
    buffer = value;
}

int get()
{
    assert(count == 1);
    count = 0;
    return buffer;
}

void *producer(void *arg)
{
    printf("producer...\n");
    int i;
    int loops = (int) arg;
    for (i = 0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        while(count == 1){
            printf("producer wait......\n");
            pthread_cond_wait(&empty, &mutex);
        }
        put(i);
        pthread_cond_signal(&fill);
        pthread_mutex_unlock(&mutex);
        printf("put: %d",i);
    }
}

void *consumer(void *arg)
{
    printf("consumer...\n");
    int i;
    int loops = (int)arg;
    for(i =0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        while(count == 0){
            printf("consumer wait.....\n");
            pthread_cond_wait(&fill, &mutex);
        }
        int tmp = get();
        printf("[%d]get: %d\n",pthread_self(),tmp);
        pthread_cond_signal(&empty);
        pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    printf("begin...\n");
     pthread_t p,p1;
    pthread_create(&p, NULL, consumer,(void *)10000);
    pthread_create(&p1, NULL, consumer, (void*)10000);

    producer((void*)20000);
    pthread_join(p, NULL);
    pthread_join(p1, NULL);
    return 0;
}
put: 19997producer wait......
[754308864]get: 19997
consumer wait.....
put: 19998producer wait......
[754308864]get: 19998
consumer wait.....
put: 19999[754308864]get: 19999

这里,两个问题就都解决了,是不是很happy。别高兴太早,这个方案虽然可用了,但不太通用,我们buffer不可能只是一个的,下面我们再改改

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define MAX 1000

int buffer[MAX];
int fills = 0;
int use   = 0;
int count = 0;

pthread_cond_t empty,fill;
pthread_mutex_t mutex;

void put(int value) 
{
    buffer[fills] = value;
    fills = (fills + 1) % MAX;
    count++;
}

int get()
{
    int tmp = buffer[use];
    use = (use + 1) % MAX;
    count--;
    return tmp;
}

void *producer(void *arg)
{
    printf("producer...\n");
    int i;
    int loops = (int) arg;
    for (i = 0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        while(count == MAX){
            printf("producer wait......\n");
            pthread_cond_wait(&empty, &mutex);
        }
        put(i);
        pthread_cond_signal(&fill);
        pthread_mutex_unlock(&mutex);
        printf("put: %d",i);
    }
}

void *consumer(void *arg)
{
    printf("consumer...\n");
    int i;
    int loops = (int)arg;
    for(i =0; i < loops; i++) {
        pthread_mutex_lock(&mutex);
        while(count == 0){
            printf("consumer wait.....\n");
            pthread_cond_wait(&fill, &mutex);
        }
        int tmp = get();
        printf("[%d]get: %d\n",pthread_self(),tmp);
        pthread_cond_signal(&empty);
        pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    printf("begin...\n");
     pthread_t p,p1;
    pthread_create(&p, NULL, consumer,(void *)10000);
    pthread_create(&p1, NULL, consumer, (void*)10000);

    producer((void*)20000);
    pthread_join(p, NULL);
    pthread_join(p1, NULL);
    return 0;
}
[-797354240]get: 19988
[-797354240]get: 19989
[-797354240]get: 19990
[-797354240]get: 19991
[-797354240]get: 19992
[-797354240]get: 19993
[-797354240]get: 19994
[-797354240]get: 19995
consumer wait.....
put: 19996put: 19997put: 19998put: 19999[-797354240]get: 19996
[-797354240]get: 19997
[-797354240]get: 19998
[-797354240]get: 19999

这样就差不多了哈,确实快了呢!!!嘿嘿

参考:

  • 操作系统导论
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!