pthread и условные переменные - PullRequest
       50

pthread и условные переменные

0 голосов
/ 23 февраля 2019

Я следую учебному пособию по pthread с здесь .

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

pthread_mutex_t count_mutex     = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t condition_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  condition_cond  = PTHREAD_COND_INITIALIZER;

void *functionCount1();
void *functionCount2();
int  count = 0;
#define COUNT_DONE  10
#define COUNT_HALT1  3
#define COUNT_HALT2  6

main()
{
   pthread_t thread1, thread2;

   pthread_create( &thread1, NULL, &functionCount1, NULL);
   pthread_create( &thread2, NULL, &functionCount2, NULL);
   pthread_join( thread1, NULL);
   pthread_join( thread2, NULL);

   exit(0);
}

void *functionCount1()
{
   for(;;)
   {
      pthread_mutex_lock( &condition_mutex );
      while( count >= COUNT_HALT1 && count <= COUNT_HALT2 )
      {
         pthread_cond_wait( &condition_cond, &condition_mutex );
      }
      pthread_mutex_unlock( &condition_mutex );

      pthread_mutex_lock( &count_mutex );
      count++;
      printf("Counter value functionCount1: %d\n",count);
      pthread_mutex_unlock( &count_mutex );

      if(count >= COUNT_DONE) return(NULL);
    }
}

void *functionCount2()
{
    for(;;)
    {
       pthread_mutex_lock( &condition_mutex );
       if( count < COUNT_HALT1 || count > COUNT_HALT2 )
       {
          pthread_cond_signal( &condition_cond );
       }
       pthread_mutex_unlock( &condition_mutex );

       pthread_mutex_lock( &count_mutex );
       count++;
       printf("Counter value functionCount2: %d\n",count);
       pthread_mutex_unlock( &count_mutex );

       if(count >= COUNT_DONE) return(NULL);
    }

}

И автор добавляет, что functioncount1() останавливается для значений между значениями COUNT_HALT1 и COUNT_HALT2.

Пример выходных данных выглядит следующим образом:

Counter value functionCount1: 1
Counter value functionCount1: 2
Counter value functionCount1: 3
Counter value functionCount2: 4
Counter value functionCount2: 5
Counter value functionCount2: 6
Counter value functionCount2: 7
Counter value functionCount1: 8
Counter value functionCount1: 9
Counter value functionCount1: 10
Counter value functionCount2: 11

Из моих наблюдений за кодом, не должно ли 3 считаться functionCount2?В цикле while в functionCount1 он вызывает wait() для любого значения , включая 3, что заставляет меня думать, что 3 следует считать functionCount2 вместо functionCount1.

Почему не так?

1 Ответ

0 голосов
/ 25 февраля 2019

Один поток может читать count только с удержанным condition_mutex (где count проверяется против COUNT_HALT1 и COUNT_HALT) или вообще без удержания мьютекса (где count проверяется с COUNT_DONE) в то время как другой поток может изменить count, удерживая только count_mutex.Этот несинхронизированный доступ приводит к неопределенному поведению, как отметил @EOF в комментариях к вопросу, поэтому код просто неверен.

При этом, даже если мы выполняем только functionCount1() без другого потока (так чтонесинхронизированный доступ не происходит), мы все равно ожидаем увидеть этот вывод:

Counter value functionCount1: 1
Counter value functionCount1: 2
Counter value functionCount1: 3

Это потому, что значение счетчика печатается после приращения, поэтому в последней итерации он видитначальное значение счетчика 2, не ждет, увеличивает счетчик, а затем печатает новое значение счетчика 3.

Обратите внимание, что в вашем исходном коде, даже игнорируя несинхронизированный доступ, равно все еще есть возможность для functionCount1 выполнить приращение от 3 до 4. Это связано с тем, что в промежутке между functionCount1 просмотром значения count 2 и принятием решения не ждать и фактически блокированием count_mutex значение может бытьувеличивается другим потоком.

Для удаления несинхронизированного доступа к count и исправления гонки, указанной в предыдущем пaragraph, вы просто полностью удаляете condition_mutex и вместо этого используете count_mutex, сохраняя его заблокированным между возвратом pthread_cond_wait() и фактическим приращением count.Это общий шаблон: мьютекс, который вы заблокировали при вызове pthread_cond_wait(), должен быть мьютексом, защищающим общее состояние, которое вы ожидаете, с помощью вашей переменной условия (здесь это общее состояние является просто переменной count):

void *functionCount1()
{
    for(;;)
    {
        pthread_mutex_lock( &count_mutex );
        while( count >= COUNT_HALT1 && count <= COUNT_HALT2 )
        {   
            pthread_cond_wait( &condition_cond, &count_mutex );
        }

        count++;
        printf("Counter value functionCount1: %d\n",count);

        if (count >= COUNT_DONE)
        {   
            pthread_mutex_unlock( &count_mutex );
            return(NULL);
        }
        pthread_mutex_unlock( &count_mutex );
    }
}

void *functionCount2()
{
    for(;;)
    {
        pthread_mutex_lock( &count_mutex );
        if( count < COUNT_HALT1 || count > COUNT_HALT2 )
        {
            pthread_cond_signal( &condition_cond );
        }

        count++;
        printf("Counter value functionCount2: %d\n",count);

        if (count >= COUNT_DONE)
        {
            pthread_mutex_unlock( &count_mutex );
            return(NULL);
        }
        pthread_mutex_unlock( &count_mutex );
    }
}

Однако это не оптимально: мьютекс удерживается заблокированным дольше, чем необходимо.Если вас не волнует произвольное чередование выходных данных между потоками, тогда вам не нужно блокировать count_mutex при вызове printf(), если вы берете локальную копию нового значения count для передачидо printf().Вы также можете использовать эту локальную копию в тесте выхода.

Кроме того, условие сигнализации необходимо проверять только после того, как functionCount2() изменилось count.pthread_mutex_signal() не нужно вызывать с удержанным мьютексом, поэтому мы можем поставить его и после printf(), используя локальную копию счета:

void *functionCount1()
{
    for(;;)
    {
        int my_count;

        pthread_mutex_lock( &count_mutex );
        while( count >= COUNT_HALT1 && count <= COUNT_HALT2 )
        {
            pthread_cond_wait( &condition_cond, &count_mutex );
        }

        count++;
        my_count = count;
        pthread_mutex_unlock( &count_mutex );

        printf("Counter value functionCount1: %d\n", my_count);

        if (my_count >= COUNT_DONE)
            return(NULL);
    }
}

void *functionCount2()
{
    for(;;)
    {
        int my_count;

        pthread_mutex_lock( &count_mutex );
        count++;
        my_count = count;
        pthread_mutex_unlock( &count_mutex );

        printf("Counter value functionCount2: %d\n", my_count);

        if ( my_count < COUNT_HALT1 || my_count > COUNT_HALT2 )
        {
            pthread_cond_signal( &condition_cond );
        }

        if (my_count >= COUNT_DONE)
            return(NULL);
    }
}

Как отмечалось выше, вывероятно, больше не будет видеть вывод printf(), упорядоченный в строгом порядке подсчета, потому что мы больше не заставляем printf () выполняться атомарно с приращением.

...