проблема производителя-потребителя: мьютекс posix дважды блокировался при использовании условной переменной? - PullRequest
0 голосов
/ 09 августа 2010

Следующий код предназначен только для того, чтобы показать, как использовать переменную условия для синхронизации потоков (одного производителя и многих потребителей) в качестве упражнений. См. Строчку для кода «usleep (100);». Когда я комментирую эту строку, кажется, что два потребительских потока заблокировали мьютекс одновременно с выходом из потока производителя, а затем ждут «cond», это условие повышения расы. Но если я раскомментирую это. Все идет хорошо (кажется).

Мой вопрос: как два пользовательских потока могут блокировать один мьютекс одновременно? Эта демонстрация также должна работать даже после того, как я не вызываю usleep ()? Спасибо за ваше время заранее.

Это вывод после того, как usleep удален из циклов в производителе. Обратите внимание на последние два «замка» в выводе.

$ ./pthread_cond 
Producer 3062414192 beginning...
Producer locked mutex self:3062414192
Producer is creating work:1
Producer finished creating work:1
Producer unlock self:3062414192
Producer locked mutex self:3062414192
Producer is creating work:2
Producer finished creating work:2
Producer unlock self:3062414192
Producer locked mutex self:3062414192
Producer is creating work:3
Producer finished creating work:3
Producer unlock self:3062414192
Producer locked mutex self:3062414192
Producer is creating work:4
Producer finished creating work:4
Producer unlock self:3062414192
Producer 3062414192 exit after creating 4 works...
produce joined,but 4 work remained
Consumer 3070806896 beginning...
Consumer locked mutex self:3070806896
to wait on cond self:3070806896
Consumer 3079199600 beginning...
Consumer locked mutex self:3079199600
to wait on cond self:3079199600

Реализован:

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

#define MAX_COUSUMER 2

#define TOTAL_WORK 4

int g_work_counter=0;

pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;

void *producer_thread(void* arg)
{
    int i;

    printf("Producer %lu beginning...\n",pthread_self());
    for(i=0;i<TOTAL_WORK;i++)
    {
        assert(pthread_mutex_lock(&mut)==0);
        printf("Producer locked mutex self:%lu\n",pthread_self());
        printf("Producer is creating work:%d\n",g_work_counter+1);
        g_work_counter++;
        printf("Producer finished creating work:%d\n",g_work_counter);
        pthread_cond_broadcast(&cond);
        assert(pthread_mutex_unlock(&mut)==0);
        printf("Producer unlock self:%lu\n",pthread_self());

        //usleep(100);
    }

    printf("Producer self:%lu exit after creating %d works...\n",pthread_self(),i);//counter starts from 0
    pthread_exit(NULL);
}

void *consumer_thread(void *arg)
{
    printf("Consumer %lu beginning...\n",pthread_self());
    //use pthread_cancel in main
    pthread_detach(pthread_self());

    while(1)
    {
        assert(pthread_mutex_lock(&mut)==0);
        printf("Consumer locked mutex self:%lu\n",pthread_self());
        printf("to wait on cond self:%lu\n",pthread_self());
        assert(pthread_cond_wait(&cond,&mut)==0);
        if(g_work_counter)
        {
            printf("Consumer %lu is performing work:%d\n",pthread_self(),g_work_counter);
            g_work_counter--;
            printf("Consumer %lu finished performing work:%d\n",pthread_self(),g_work_counter+1);
        }
        assert(pthread_mutex_unlock(&mut)==0);
        printf("Consumer unlock self:%lu\n",pthread_self());
    }

    //no output (pthread_cancel is called)
    printf("Consumer %lu exit...\n",pthread_self());
    pthread_exit(NULL);
}

int main(int argc,char* argv[])
{
    pthread_t producer;
    pthread_t consumers[MAX_COUSUMER];
    int i;

    for(i=0;i<MAX_COUSUMER;i++)
    {
        if(pthread_create(&consumers[i],NULL,consumer_thread,NULL)!=0)
        {
            printf("pthread_create failed for consumer_thread %d\n",i);
        }
    }

    pthread_create(&producer,NULL,producer_thread,NULL);

    if(pthread_join(producer,NULL)!=0)
    {
        printf("pthread_join failed for producer_thread %lu\n",consumers[i]);
    }
    printf("producer joined,but %d work remained\n",g_work_counter);

    //wait for the consumers
    while(g_work_counter>0)
        ;

    //cancel the consumer,for they are detached
    for(i=0;i<MAX_COUSUMER;i++)
    {
        if(pthread_cancel(consumers[i])!=0)
        {
            printf("pthread_cancel failed for consumer_thread %d\n",i);
        }
    }

    pthread_mutex_destroy(&mut);
    pthread_cond_destroy(&cond);
    return 0;
}

Ответы [ 3 ]

3 голосов
/ 09 августа 2010

Как говорит Borealid , блокировка снята, пока поток ожидает переменную условия. Ваша потребительская функция должна выглядеть так:

    /* Wait for some work to be available */
    pthread_mutex_lock(&mut);
    while (g_work_counter == 0)
    {
        pthread_cond_wait(&cond, &mut);
    }

    /* Have lock, g_work_counter is > 0, so do work */
    while (g_work_counter > 0)
    {
        g_work_counter--;
    }

    pthread_mutex_unlock(&mut);

(pthread_cond_wait() должен всегда использоваться внутри вращающегося цикла с циклом)

3 голосов
/ 09 августа 2010

Когда поток ожидает условия, он освобождает блокировку.Когда оно просыпается, оно снова его получает.В этом коде потребитель должен только ждать, если буфер пуст.

Другая проблема заключается в main, на самом деле, с этой строкой: while(g_work_counter>0).В этот момент у вас нет блокировки, поэтому проверять g_work_counter небезопасно.Я тоже не очень уверен насчет pthread_detach(pthread_self());.Разве это не должно вызываться main на его собственном дочернем элементе?

Как общее примечание: если вы хотите проверять наличие взаимоблокировок и иным образом отлаживать код pthreads, вы должны использовать функции pthread_mutexattr_fooсоздать мьютекс с проверкой ошибок и проверить возвращаемое значение, используя не только assert(==0).

1 голос
/ 09 августа 2010

Я бы предложил, чтобы вы избегали использования API pthread_cancel (). Очень сложно использовать pthread_cancel () без утечек ресурсов, тупиков и несоответствий в вашей программе.

Как правило: Используйте отдельные темы для «одноразовых / мне наплевать на результаты / завершение чего-либо». Для других вещей используйте «нормальные потоки», которые отключаются совместным способом (проверяя флаг).

Итак, в вашем примере вы хотите, чтобы вся работа потреблялась до выхода из основной программы. Не создавайте потребителей как отдельных и замените цикл, который отменил все потребители с циклом, выполняющим pthread_join () для всех потребителей.

...