首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> C 語言問答

C語言實現生產者-消費者模型:循環隊列 + 信號量同步

     編輯:C 語言問答

經典並發模式:有界緩衝區的 C 語言實現

問題背景

讀者-寫者問題的變體:多生產者向共享緩衝區寫入數據,多消費者讀取。核心約束:

  • 緩衝區滿時生產者阻塞
  • 緩衝區空時消費者阻塞
  • 對緩衝區訪問互斥

核心數據結構:循環隊列


typedef struct {
    void **buffer;      // 數據指針數組
    size_t capacity;    // 隊列容量
    size_t size;        // 當前元素數
    size_t head;        // 讀指針
    size_t tail;        // 寫指針
    pthread_mutex_t mutex;  // 互斥鎖
    sem_t empty_slots;      // 空槽位信號量
    sem_t filled_slots;     // 已填槽位信號量
} circular_queue_t;

完整實現(POSIX 線程 + 信號量)

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

#define QUEUE_CAPACITY 10
#define NUM_PRODUCERS 2
#define NUM_CONSUMERS 3
#define ITEMS_PER_PRODUCER 20

typedef struct {
    int *buffer[QUEUE_CAPACITY];
    size_t head, tail, count;
    pthread_mutex_t mutex;
    sem_t empty, full;
} circle_queue_t;

circle_queue_t g_queue = {
    .head = 0, .tail = 0, .count = 0,
    .mutex = PTHREAD_MUTEX_INITIALIZER,
    // 信號量需運行時初始化
};

void queue_init(circle_queue_t *q) {
    sem_init(&q->empty, 0, QUEUE_CAPACITY);  // 初始全空
    sem_init(&q->full, 0, 0);                // 初始無數據
}

void queue_destroy(circle_queue_t *q) {
    sem_destroy(&q->empty);
    sem_destroy(&q->full);
    pthread_mutex_destroy(&q->mutex);
}

int enqueue(circle_queue_t *q, int *item) {
    sem_wait(&q->empty);           // 等待空槽 (P操作)
    pthread_mutex_lock(&q->mutex); // 獲取互斥鎖
    
    q->buffer[q->tail] = item;
    q->tail = (q->tail + 1) % QUEUE_CAPACITY;
    q->count++;
    
    pthread_mutex_unlock(&q->mutex); // 釋放互斥鎖
    sem_post(&q->full);              // 通知有數據 (V操作)
    return 0;
}

int dequeue(circle_queue_t *q, int **item) {
    sem_wait(&q->full);            // 等待數據
    pthread_mutex_lock(&q->mutex);
    
    *item = q->buffer[q->head];
    q->head = (q->head + 1) % QUEUE_CAPACITY;
    q->count--;
    
    pthread_mutex_unlock(&q->mutex);
    sem_post(&q->empty);           // 通知有空槽
    return 0;
}

void *producer(void *arg) {
    int id = *(int*)arg;
    for (int i = 0; i < ITEMS_PER_PRODUCER; i++) {
        int *item = malloc(sizeof(int));
        *item = id * 1000 + i;  // 編碼生產者ID和序號
        enqueue(&g_queue, item);
        printf("Producer %d: produced %d\n", id, *item);
        usleep(rand() % 100000);  // 模擬生產耗時
    }
    return NULL;
}

void *consumer(void *arg) {
    int id = *(int*)arg;
    while (1) {
        int *item;
        dequeue(&g_queue, &item);
        printf("Consumer %d: consumed %d\n", id, *item);
        free(item);  // 消費後釋放
        usleep(rand() % 150000); // 模擬消費耗時
    }
    return NULL;
}

int main() {
    queue_init(&g_queue);
    srand(time(NULL));
    
    pthread_t producers[NUM_PRODUCERS], consumers[NUM_CONSUMERS];
    int prod_ids[NUM_PRODUCERS], cons_ids[NUM_CONSUMERS];
    
    for (int i = 0; i < NUM_PRODUCERS; i++) {
        prod_ids[i] = i;
        pthread_create(&producers[i], NULL, producer, &prod_ids[i]);
    }
    for (int i = 0; i < NUM_CONSUMERS; i++) {
        cons_ids[i] = i;
        pthread_create(&consumers[i], NULL, consumer, &cons_ids[i]);
    }
    
    for (int i = 0; i < NUM_PRODUCERS; i++)
        pthread_join(producers[i], NULL);
    
    // 給消費者處理剩餘數據的時間
    sleep(2);
    queue_destroy(&g_queue);
    return 0;
}

同步機制圖解

信號量 empty (初始=容量)     信號量 full (初始=0)       互斥鎖 mutex
     ↑                            ↑                        ↑
生產者:                         消費者:                   保護:
P(empty)  --等待空槽-->         P(full)  --等待數據-->    lock
                                  讀取數據                 修改隊列
V(full)   --通知有數據-->       V(empty) --通知有空槽-->   unlock

關鍵設計決策

進階優化方向

  1. 無鎖隊列:CAS 原子操作實現無鎖環形緩衝區(單生產單消費)
  • 編譯運行

    gcc -std=c11 -pthread -o producer_consumer producer_consumer.c
    ./producer_consumer
    
    1. 上一篇文章:
    2. 下一篇文章:
    Copyright © 程式碼之家 All Rights Reserved
    決策點選擇理由
    同步原語信號量 + 互斥鎖信號量處理資源計數,互斥鎖保護臨界區,職責分離
    隊列類型循環數組O(1) 入隊出隊,無內存碎片,緩存友好
    數據所有權生產者分配,消費者釋放明確所有權轉移,避免雙重釋放/洩漏
    忙等待 vs 阻塞信號量阻塞CPU 效率高,無自旋浪費
    批量操作一次 enqueue/dequeue 多個項目,減少系統調用開銷
    優先級繼承避免優先級反轉,實時系統必備
    內存池預分配項目內存,消除 malloc/free 開銷