使用条件变量的正确方法是什么?

What is the proper way to use conditional variables?

提问人:Samuel Afon 提问时间:10/28/2023 更新时间:10/28/2023 访问量:76

问:

我的赋值提供的运行代码在运行时使用高百分比的 CPU。目标是通过在生产者消费者问题中实现条件变量来减少该数量。

我按照我所能提供的说明进行操作,在互斥锁解锁之前在生产者端添加一个 wait() 函数,然后从 if-else 语句的 else 部分发送信号。

            // if buffer is full, release mutex lock and check again
            if (shared_count == NITEMS){

                 // wait when full
                pthread_cond_wait(&cond1, &mutex);
                pthread_mutex_unlock(&mutex);

            }
            else{
                //signal
                pthread_cond_signal(&cond1);
                break;
            }

我在消费者方面也做了同样的事情。

问题是我的整体代码不再提供任何输出。

我在下面包含了完整的代码,以防万一

/* minor3.c - using producer-consumer paradigm. */

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

#define NITEMS 10       // number of items in shared buffer

// shared variables
char shared_buffer[NITEMS]; // echo buffer
int shared_count;       // item count

pthread_mutex_t mutex;      // pthread mutex
unsigned int prod_index = 0;    // producer index into shared buffer
unsigned int cons_index = 0;    // consumer index into shard buffer

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

// Consumer variables
pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond2 = PTHREAD_COND_INITIALIZER;

int main()
{
    pthread_t prod_tid, cons_tid1, cons_tid2;

    // initialize pthread variables
    pthread_mutex_init(&mutex, NULL);

    // initialize condition variables
    pthread_cond_init(&cond1, NULL);
    pthread_cond_init(&cond2, NULL);

    // start producer thread
    pthread_create(&prod_tid, NULL, producer, NULL);

    // start consumer threads
    pthread_create(&cons_tid1, NULL, consumer, NULL);
    pthread_create(&cons_tid2, NULL, consumer, NULL);

    // wait for threads to finish
    pthread_join(prod_tid, NULL);
    pthread_join(cons_tid1, NULL);
    pthread_join(cons_tid2, NULL);

    // clean up
    pthread_mutex_destroy(&mutex);

    pthread_cond_destroy(&cond1);
    pthread_cond_destroy(&cond2);

    return 0;
}

// producer thread executes this function
void * producer(void *arg)
{
    char key;

    printf("Enter text for producer to read and consumer to print, use Ctrl-C to exit.\n\n");

    // this loop has the producer read data in from stdin and place the read data on the shared buffer
    while (1)
    {
        // read input key
        scanf("%c", &key);

        // this loop is used to poll the shared buffer to see if it is full:
        // -- if full, unlock and loop again to keep polling
        // -- if not full, keep locked and proceed to place character on shared buffer
        while (1)
        {
            // acquire mutex lock
            pthread_mutex_lock(&mutex);

            // if buffer is full, release mutex lock and check again
            if (shared_count == NITEMS){

                 // wait when full
                pthread_cond_wait(&cond1, &mutex);
                pthread_mutex_unlock(&mutex);

            }
            else{
                //signal
                pthread_cond_signal(&cond1);
                break;
            }

        }

        // store key in shared buffer
        shared_buffer[prod_index] = key;

        // update shared count variable
        shared_count++;

        // update producer index
        if (prod_index == NITEMS - 1)
            prod_index = 0;
        else
            prod_index++;

        // release mutex lock
        pthread_mutex_unlock(&mutex); 
    }

    return NULL;
}

// consumer thread executes this function
void * consumer(void *arg)
{
    char key;

    long unsigned int id = (long unsigned int)pthread_self();

    // this loop has the consumer get data from the shared buffer and print to stdout
    while (1)
    {
        // this loop is used to poll the shared buffer to see if it is empty:
        // -- if empty, unlock and loop again to keep polling
        // -- if not empty, keep locked and proceed to get character from shared buffer
        while (1)
        {
            // acquire mutex lock
            pthread_mutex_lock(&mutex);

            // if buffer is empty, release mutex lock and check again
            if (shared_count == 0){
                
                pthread_cond_wait(&cond2, &mutex);
                pthread_mutex_unlock(&mutex);

                }
            else{

                pthread_cond_signal(&cond2);
                break;

                }
        }

        // read key from shared buffer
        key = shared_buffer[cons_index];

        // echo key
        printf("consumer %lu: %c\n", (long unsigned int) id, key);

        // update shared count variable
        shared_count--;

        // update consumer index
        if (cons_index == NITEMS - 1)
            cons_index = 0;
        else
            cons_index++;

        // release mutex lock
        pthread_mutex_unlock(&mutex);
    }

    return NULL;
}



c 互斥 锁争用 producer-consumer 条件变量

评论

0赞 Ted Lyngmo 10/28/2023
@DavidSchwartz啊,谢谢,我错过了循环。解锁/重新锁定可能会导致一些奇怪的效果。
1赞 John Bollinger 10/28/2023
术语:它是“条件变量”,而不是“条件变量”。

答:

4赞 David Schwartz 10/28/2023 #1

你有两个错误。

首先,您永远不会设置为零。您可以增加、减少和测试它。但你永远不会初始化它。shared_count

其次,消费者向消费者发出条件变量的信号,反之亦然。您需要消费者向生产者发出信号,反之亦然!

最后,虽然这不是一个错误,但你的循环不必要地解锁了互斥锁,只是为了在下一次迭代时立即再次锁定它。在内部循环之外解锁互斥锁更有意义。

下面是修复了错误和更传统的循环结构的代码:

/* minor3.c - using producer-consumer paradigm. */
  
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define NITEMS 10       // number of items in shared buffer

// shared variables
char shared_buffer[NITEMS]; // echo buffer
int shared_count = 0;       // item count

pthread_mutex_t mutex;      // pthread mutex
unsigned int prod_index = 0;    // producer index into shared buffer
unsigned int cons_index = 0;    // consumer index into shard buffer

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

// Consumer variables
pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond2 = PTHREAD_COND_INITIALIZER;

int main()
{
    pthread_t prod_tid, cons_tid1, cons_tid2;

    // initialize pthread variables
    pthread_mutex_init(&mutex, NULL);

    // initialize condition variables
    pthread_cond_init(&cond1, NULL);
    pthread_cond_init(&cond2, NULL);

    // start producer thread
    pthread_create(&prod_tid, NULL, producer, NULL);

    // start consumer threads
    pthread_create(&cons_tid1, NULL, consumer, NULL);
    pthread_create(&cons_tid2, NULL, consumer, NULL);

    // wait for threads to finish
    pthread_join(prod_tid, NULL);
    pthread_join(cons_tid1, NULL);
    pthread_join(cons_tid2, NULL);

    // clean up
    pthread_mutex_destroy(&mutex);

    pthread_cond_destroy(&cond1);
    pthread_cond_destroy(&cond2);

    return 0;
}


// producer thread executes this function
void * producer(void *arg)
{
    char key;

    printf("Enter text for producer to read and consumer to print, use Ctrl-C to exit.\n\n");

    // this loop has the producer read data in from stdin and place the read data on the shared buffer
    while (1)
    {
        // read input key
        scanf("%c", &key);

        pthread_mutex_lock(&mutex);

        while (shared_count == NITEMS)
            pthread_cond_wait(&cond1, &mutex);

        // store key in shared buffer
        shared_buffer[prod_index] = key;

        // update shared count variable
        shared_count++;

        // update producer index
        if (prod_index == (NITEMS - 1))
            prod_index = 0;
        else
            prod_index++;

        // release mutex lock
        pthread_cond_signal(&cond2);
        pthread_mutex_unlock(&mutex); 
    }

    return NULL;
}

// consumer thread executes this function
void * consumer(void *arg)
{
    char key;

    long unsigned int id = (long unsigned int)pthread_self();

    // this loop has the consumer get data from the shared buffer and print to stdout
    while (1)
    {
        // this loop is used to poll the shared buffer to see if it is empty:
        // -- if empty, unlock and loop again to keep polling
        // -- if not empty, keep locked and proceed to get character from shared buffer
        pthread_mutex_lock(&mutex);
        while (shared_count == 0)
            pthread_cond_wait(&cond2, &mutex);

        // read key from shared buffer
        key = shared_buffer[cons_index];

        // echo key
        printf("consumer %lu: %c\n", (long unsigned int) id, key);

        // update shared count variable
        shared_count--;

        // update consumer index
        if (cons_index == (NITEMS - 1))
            cons_index = 0;
        else
            cons_index++;
        pthread_cond_signal(&cond1);

        // release mutex lock
        pthread_mutex_unlock(&mutex);
    }

    return NULL;
}

评论

0赞 ikegami 10/28/2023
您在没有解释的情况下修复的一件事是,它可能会虚假唤醒,因此应始终在循环中使用。pthread_cond_waitwhile ( !in_desired_state() ) pthread_cond_wait( &cond, &mutex );
0赞 David Schwartz 10/28/2023
@ikegami 这在代码中不是问题,因为它已经使用了测试谓词的循环,它只是以一种不寻常的方式这样做,具有过多的互斥锁/锁定周期。