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 下我沒有發現這個問題,這個問題留待以後深究。另外我們還可以採用不同的測試方案,對程序傳入不同的參數,觀察運行結果。