You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Pthread的C语言多线程生产者消费者代码异常求助

问题描述

原本预期每个生产者线程会独立向缓冲区添加数据,但实际运行时同一个线程持续填充缓冲区,推测是直到发生上下文切换才会切换线程。只能使用pthread的条件变量和互斥锁,不能使用信号量或sleep,请问这是否是问题的原因?运行环境为Ubuntu 22.04下的C语言。

代码实现

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

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t fill = PTHREAD_COND_INITIALIZER;
pthread_cond_t empty = PTHREAD_COND_INITIALIZER;

int buffer[10];
int cnt = 0;  // # of items in the buffer
int in = 0;  // index to add an item to buffer
int out = 0;  // index to read an item from buffer

void *producer();  //producer function
void *consumer();  //consumer function

int main(void)
{
    srand(time(NULL));
    pthread_t Pthread[5];
    pthread_t Cthread[5];
    for (int i=0; i<5; i++) {
        pthread_create(&Pthread[i], NULL, producer, NULL);
    }
    for (int i=0; i<5; i++) {
        pthread_create(&Cthread[i], NULL, consumer, NULL);
        }
    for (int i=0; i<5; i++) {
        pthread_join(Pthread[i], NULL);
    }
    for (int i=0; i<5; i++) {
        pthread_join(Cthread[i], NULL);
    }
    return 0;
}

void *producer(void *arg){
    while(1){
        int next_produced = rand();  // create a random integer
        pthread_mutex_lock(&mutex);
        //======== critical section =============
        while (cnt == 10) {
            pthread_cond_wait(&empty, &mutex);
        }
        buffer[in] = next_produced;  // add the item
        in = (in + 1) % 10;  // move to next index
        cnt++;
        printf("Producer thread: %lu adds the value : %d\n",
                pthread_self(), next_produced);
        pthread_cond_signal(&fill);
        //======== critical section =============
        pthread_mutex_unlock(&mutex);
    }
}

void *consumer(void *arg){
    while(1){
        pthread_mutex_lock(&mutex);
        //======== critical section =============
        while (cnt == 0){
            pthread_cond_wait(&fill, &mutex);
        }
        int next_consumed = buffer[out];  // read an item from buffer
        buffer[out] = 0;  // remove the item from buffer
        out = (out + 1) % 10;  // move to next index
        cnt--;
        printf("Consumer thread: %lu deletes the value : %d\n",
                pthread_self(),next_consumed);
        pthread_cond_signal(&empty);
        //======== critical section =============
        pthread_mutex_unlock(&mutex);
    }
}

部分运行结果

Producer thread: 140109028513344 adds the value : 1457620057
Producer thread: 140109028513344 adds the value : 1109246666
Producer thread: 140109028513344 adds the value : 34472492
Producer thread: 140109028513344 adds the value : 1658551151
Producer thread: 140109028513344 adds the value : 1646031817
Producer thread: 140109028513344 adds the value : 2114864384
Producer thread: 140109028513344 adds the value : 2019817090
Producer thread: 140109028513344 adds the value : 90771015
Producer thread: 140109028513344 adds the value : 1977802548
Producer thread: 140109028513344 adds the value : 1337378708
Consumer thread: 140108921554496 deletes the value : 1457620057
Consumer thread: 140108921554496 deletes the value : 1109246666
Consumer thread: 140108921554496 deletes the value : 34472492
Consumer thread: 140108921554496 deletes the value : 1658551151
Consumer thread: 140108921554496 deletes the value : 1646031817
Consumer thread: 140108921554496 deletes the value : 2114864384
Consumer thread: 140108921554496 deletes the value : 2019817090
Consumer thread: 140108921554496 deletes the value : 90771015
Consumer thread: 140108921554496 deletes the value : 1977802548
Consumer thread: 140108921554496 deletes the value : 1337378708
Producer thread: 140109036906048 adds the value : 662295424
Consumer thread: 140108913161792 deletes the value : 662295424
Producer thread: 140108946732608 adds the value : 1952746478
Producer thread: 140108946732608 adds the value : 1782607749
解答

你的推测部分正确,但还有一个关键代码问题共同导致了这个现象:

1. 操作系统线程调度特性是直接诱因

Linux的线程调度器默认会尽量减少上下文切换的开销,当一个线程释放互斥锁后,如果它很快再次尝试获取锁,调度器大概率会让它继续持有CPU——因为切换线程需要保存/恢复寄存器等状态,代价较高。所以单个生产者线程会连续获取锁,填充缓冲区直到触发阻塞(比如缓冲区满)或者调度器强制进行上下文切换。

2. pthread_cond_signal的使用放大了这个问题

你当前使用pthread_cond_signal只会唤醒一个等待在对应条件变量上的线程:

  • 生产者添加数据后调用signal,只会唤醒一个等待的消费者;
  • 消费者取走数据后调用signal,也只会唤醒一个等待的生产者。

如果被唤醒的刚好是刚才释放锁的那个线程(调度器倾向于这样),就会导致该线程再次抢到锁,继续执行,进一步加剧了单个线程连续运行的情况。

修复方案

把代码中的pthread_cond_signal替换成pthread_cond_broadcast:

  • 生产者中的pthread_cond_signal(&fill);改成pthread_cond_broadcast(&fill);
  • 消费者中的pthread_cond_signal(&empty);改成pthread_cond_broadcast(&empty);

pthread_cond_broadcast会唤醒所有等待在条件变量上的线程,这样多个等待的生产者/消费者都会被唤醒参与锁竞争,调度器有更大的概率选择不同的线程执行,从而避免单个线程持续填充缓冲区的情况。

注意:即使修改后,调度器仍可能偶尔让同一个线程连续抢到锁,但整体上多线程的执行会更加均衡。另外,rand()函数并非线程安全,若需线程安全的随机数生成,可以使用rand_r()或者arc4random()。

内容的提问来源于stack exchange,提问作者wdh4804

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 08:05:05