проблем производител-потребител: posix mutex се заключи два пъти при използване на променлива на условието?

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

Въпросът ми е как две потребителски нишки могат да заключат един мютекс едновременно? Тази демонстрация също трябва да работи дори след като не извикам usleep()? Благодаря за отделеното време предварително.

Това е изходът, след като usleep е премахнат от циклите в producer. Моля, обърнете внимание на последните две „lock“ в изхода.

$ ./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;
}

person schemacs    schedule 09.08.2010    source източник
comment
Харесва ми вдлъбнатината на вашия код!   -  person Praveen S    schedule 09.08.2010


Отговори (3)


Когато нишка изчаква по условие, тя освобождава заключването. Когато бъде събудено, то го придобива отново. В този код потребителят трябва да чака само ако буферът е празен.

Друг проблем се крие в main, всъщност, с този ред: while(g_work_counter>0). В този момент нямате ключалката, така че не е безопасно да проверите g_work_counter. Също така не съм много сигурен за pthread_detach(pthread_self());. Не трябва ли това да бъде извикано от main на собственото си дете?

Като обща бележка, ако искате да проверите за блокирания и по друг начин да отстраните грешки в кода на pthreads, трябва да използвате функциите pthread_mutexattr_foo, за да настроите mutex за проверка на грешки и да проверите върнатата стойност с повече от assert(==0).

person Borealid    schedule 09.08.2010
comment
Благодаря. Както казахте, потребителят трябва да изчака само ако буферът е празен. Намерих това за pthread_detach в страницата на ръководството: Функцията pthread_detach() трябва да посочи на изпълнението, че паметта за нишката на нишката може да бъде възстановена, когато тази нишка приключи, така че няма значение кой извиква това (този родител или самият той) . И също така ви благодаря, че ми напомнихте за pthread_mutexattr_* за проверка на mutex. - person schemacs; 09.08.2010
comment
вместо да отделяте, трябва да използвате атрибута към pthread_create, за да създадете нишката, отделена от самото начало. - person Jens Gustedt; 09.08.2010
comment
Да, това е по-добре, отколкото сам да отделя нишката suddenly. - person schemacs; 09.08.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() трябва винаги да се използва във въртящ се докато цикъл като този).

person caf    schedule 09.08.2010
comment
Да, той е прав. Трябва да поставям pthread_cond_wait в скоби в един цикъл (проверявам буфера, след това вземам отново заключването, когато буферът не е празен).*винаги по този начин* е добър навик за кодиране. Благодаря. - person schemacs; 09.08.2010
comment
Но след като преработих, както казахте, потребителската нишка на тази програма спира на pthread_mutex_lock дори след като pthread_cancel върне 0 и pthread_mutex_destroy се провали някога? - person schemacs; 09.08.2010

Бих предложил да се пазите от pthread_cancel() API. Много е трудно да се използва pthread_cancel() без въвеждане на изтичане на ресурси, задънени блокировки и несъответствия във вашата програма.

Като правило за удар: Използвайте отделени нишки за неща от типа „еднократно/не ме интересуват резултатите/завършването на нещо“. За други неща използвайте "нормални нишки", които се изключват по кооперативен начин (чрез проверка на флаг).

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

person Frank Meerkötter    schedule 09.08.2010
comment
Да, pthread_cancel просто изпратете заявка,The target thread's cancelability state and type determines when the cancellation takes effect. - person schemacs; 09.08.2010