pthread-消费者/生产者模型实现

本文介绍了使用pthread库在多线程编程中实现消费者/生产者模型,强调了资源池管理、线程同步和避免死锁的关键点。示例代码采用环形缓冲区和semaphore,展示了在Mac和Ubuntu上的运行差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

pthread-消费者/生产者模型实现

消费者/生产者模型是多线程编程开发的常用模型,该模型通过平衡生产者线程和消费者线程的工作能力来提高程序整体的数据处理能力。
设计该模型要注意以下几项:
- 资源池一般是有限的,访问资源是要加锁,访问完毕时记得解锁
- 生产者需要在资源池未满的情况下才能生产产品
- 消费者需要在资源池不空的情况下才能消费产品
- 设计时应考虑如何避免死锁问题
下面的例子是采用 semaphore,资源池为环形缓冲区,来实现消费者/生产者模型。代码如下:

#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <fcntl.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ERROR(func, no) { \
        fprintf(stderr, "%s: %s\n", func, strerror(no)); \
        exit(EXIT_FAILURE); \
    }

#define DEFAULT_CONSUMER_CNT    1
#define DEFAULT_PRODUCER_CNT    3
#define DEFAULT_BUFFER_SIZE 10


static size_t in; //  producer's current pos
static size_t out; // consumer's current pos
static size_t consumer_id; // current product id for consumer 
static size_t producer_id; // current product id for producer
static size_t consumer_cnt; // count of consumers
static size_t producer_cnt; // count of producers
static size_t buff_size; // resource buffer size

static int* g_buffer = NULL; // pointer to resource buffer
static pthread_t* g_thread = NULL; // pointer to thread IDs

static pthread_mutex_t g_mutex; //mutex
static sem_t* g_sem_empty_ptr = NULL; // semaphore for consumer
static sem_t* g_sem_full_ptr = NULL; // semaphore for producer

void* consume(void* arg) {
    int id = *(int*)arg;
    free(arg);

    while(1) {
        printf("Consumer[%d] waits buffer not empty\n", id);
        sem_wait(g_sem_empty_ptr);
        pthread_mutex_lock(&g_mutex);

        int i, flag;
        for(i=0; i<buff_size; ++i) {
            printf("%2d:", i);
            if(0 == g_buffer[i]) { 
                printf(" nil");
                if(out == i) {
                    out = (out+1)%buff_size;
                    printf("\n");
                    flag = 1;
                    continue;
                }
            } else {
                printf(" %d", g_buffer[i]);
            }
            if(out == i) {  
                printf("\t<-- consume[%d]", id);
                flag = 0;
            }
            printf("\n");
        }
        if(0 == flag) {
            consumer_id = g_buffer[out];
            printf("Consumer[%d] begins consuming product %lu\n", id, consumer_id);
            g_buffer[out] = 0;
            out = (out+1)%buff_size;
            printf("Consumer[%d] ends consuming product %lu\n", id, consumer_id);
            pthread_mutex_unlock(&g_mutex);
            sem_post(g_sem_full_ptr);
        } else {
            printf("*************No product to consume\n"); //mac 下回執行到該處
            pthread_mutex_unlock(&g_mutex);
        }
        sleep(1);
    }

    return NULL;
}


void* produce(void* arg) {
    int id = *(int*)arg;
    free(arg);

    while(1) {
        printf("Producer[%d] waits buffer not full\n", id);
        sem_wait(g_sem_full_ptr);
        pthread_mutex_lock(&g_mutex);
        int i, flag;
        for(i=0; i<buff_size; ++i) {
            printf("%2d:", i);
            if(0 == g_buffer[i]) {
                printf(" nil");
            } else {
                printf(" %d", g_buffer[i]);
                if(in == i) {
                    in = (in+1)%buff_size;
                    printf("\n");
                    flag = 1;
                    continue;
                }
            }
            if(in == i) {
                printf("\t<-- produce[%d]", id);
                flag = 0;
            }
            printf("\n");
        }
        if(0 == flag) {
            g_buffer[in] = ++producer_id;
            printf("Producer[%d] begins to produce product %lu\n", id, producer_id);
            in = (in+1)%buff_size;
            printf("Producer[%d] ends to produce product %lu\n", id, producer_id);
            pthread_mutex_unlock(&g_mutex);
            sem_post(g_sem_empty_ptr);
        } else {
            printf("*************No buffer to product\n");//mac 下回執行到該處
            pthread_mutex_unlock(&g_mutex);
        }
        sleep(1);
    }

    return NULL;
}


int main(int argc, char** argv)
{
        consumer_cnt = DEFAULT_CONSUMER_CNT;
        producer_cnt = DEFAULT_PRODUCER_CNT;
    buff_size = DEFAULT_BUFFER_SIZE;
        char* prog = argv[0];
        int ch; 
        while ((ch = getopt(argc, argv, "b:c:p:")) != -1) {
             switch (ch) {
             case 'b':
                     buff_size = atoi(optarg);
                     //printf("-b option: %s\n", optarg);
             case 'c':
                     consumer_cnt = atoi(optarg);
                     //printf("-c option: %s\n", optarg);
                     break;
             case 'p':
                     producer_cnt = atoi(optarg);
                     //printf("-p option: %s\n", optarg);
                     break;
             case '?':
             default:
                     printf("Usage: %s [-b buffersize] [-p producer_cnt] [-c consumer_cnt]\n"
                                "\tdefault buffersize=10, producer_cnt=3, consumer_cnt=1\n", prog);
                     exit(EXIT_FAILURE);
             }   
        }

    g_buffer = (int*)malloc(buff_size*sizeof(int));
    g_thread = (pthread_t*)malloc((consumer_cnt+producer_cnt)*sizeof(pthread_t));
    memset(g_buffer, 0, buff_size*sizeof(int));
    memset(g_thread, 0, (consumer_cnt+producer_cnt)*sizeof(pthread_t));

    g_sem_full_ptr = (sem_t*)malloc(sizeof(sem_t));
    g_sem_empty_ptr = (sem_t*)malloc(sizeof(sem_t));

// For Mac
    g_sem_full_ptr = sem_open("sem_producer", O_CREAT, 0, buff_size); //set semaphore full initially to the buffer length 
    g_sem_empty_ptr = sem_open("sem_consumer", O_CREAT, 0, 0); //set semaphore full initially to 0

 // For Linux   
 // sem_init(g_sem_full_ptr, 0, buff_size); //set semaphore full initially to the buffer length 
 // sem_init(g_sem_empty_ptr, 0, 0); //set semaphore full initially to 0

    pthread_mutex_init(&g_mutex, NULL);

    int i, ret;
    for(i=0; i<consumer_cnt; ++i) {
        void *arg = malloc(sizeof(int));
        memcpy(arg, &i, sizeof(int));
        ret = pthread_create(&g_thread[i], NULL, consume, arg);
        if(ret) {
            ERROR("pthread_create", ret);
        }
    }

    for(i=0; i<producer_cnt; ++i) {
        void *arg = malloc(sizeof(int));
        memcpy(arg, &i, sizeof(int));
        ret = pthread_create(&g_thread[i+consumer_cnt], NULL, produce, arg);
        if(ret) {
            ERROR("pthread_create", ret);
        }
    }

    for(i=0; i<consumer_cnt+producer_cnt; ++i) {
        ret = pthread_join(g_thread[i], NULL);
        if(ret) {
            ERROR("pthread_join", ret);
        }
    }

// For Mac
    sem_close(g_sem_full_ptr);
    sem_close(g_sem_empty_ptr);
    sem_unlink("sem_consumer");
    sem_unlink("sem_producer");

// For Linux    
//  sem_destroy(g_sem_full_ptr);
//  sem_destroy(g_sem_empty_ptr);

    pthread_mutex_destroy(&g_mutex);

    free(g_buffer);
    free(g_thread);
    free(g_sem_full_ptr);
    free(g_sem_empty_ptr);

    exit(EXIT_SUCCESS);
}

编译运行

gcc -g -o test pc-demo.c -lpthread

Mac运行效果:
这里写图片描述
Ubuntu 运行效果:
这里写图片描述

对比在两种 OS 下代码略有不同,原因主要是 mac 下没有 sem_init和 em_destroy
函数,也就是说 mac 下不能创建匿名信号量。而 从mac 下的运行结果可以发现程序出现了不应该出现的情况:消费者获得sem_consumer信号量,但是却发现缓冲区为空;同样生产者获得了 sem_producer 信号量,但是却发现缓冲区已满。Ubuntu 下我没有发现这个问题,这个问题留待以后深究。另外我们还可以采用不同的测试方案,对程序传入不同的参数,观察运行结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值