Почему sleep () после получения pthread_mutex_lock заблокирует всю программу? - PullRequest
4 голосов
/ 11 ноября 2011

В моей тестовой программе я запускаю два потока, каждый из которых выполняет следующую логику:

    1) pthread_mutex_lock()
    2) sleep(1)
    3) pthread_mutex_unlock()

Однако я обнаружил, что через некоторое время один из двух потоков навсегда заблокирует pthread_mutex_lock (), тогда как другой поток будет работать нормально. Это очень странное поведение, и я думаю, что это может быть серьезной проблемой. Руководством по Linux, sleep () не запрещается, когда получен pthread_mutex_t. Итак, мой вопрос: это реальная проблема или есть какая-то ошибка в моем коде?

Ниже приводится тестовая программа. В коде выходные данные 1-го потока направляются в stdout, а выходные данные второго потока - в stderr. Таким образом, мы можем проверить эти два разных вывода, чтобы увидеть, заблокирован ли поток.

Я проверил его на ядре Linux (2.6.31) и (2.6.9). Оба результата одинаковы.

//=======================  Test Program  ===========================
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>

#define THREAD_NUM 2
static int data[THREAD_NUM];
static int sleepFlag = 1;

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static void * threadFunc(void *arg)
{
        int* idx = (int*) arg;
        FILE* fd = NULL;
        if (*idx == 0)  
                fd = stdout;
  else
          fd = stderr;

        while(1) {
                fprintf(fd, "\n[%d]Before pthread_mutex_lock is called\n", *idx);
    if (pthread_mutex_lock(&mutex) != 0) {
                exit(1);            
    }
          fprintf(fd, "[%d]pthread_mutex_lock is finisheded. Sleep some time\n", *idx);
          if (sleepFlag == 1)
             sleep(1);
          fprintf(fd, "[%d]sleep done\n\n", *idx);


          fprintf(fd, "[%d]Before pthread_mutex_unlock is called\n", *idx);    
    if (pthread_mutex_unlock(&mutex) != 0) {
                exit(1);
    }
    fprintf(fd, "[%d]pthread_mutex_unlock is finisheded.\n", *idx);    
        }
}

// 1. compile
//    gcc -o pthread pthread.c -lpthread
// 2. run
//    1) ./pthread sleep 2> /tmp/error.log     # Each thread will sleep 1 second after it acquires pthread_mutex_lock
//       ==> We can find that /tmp/error.log will not increase.
//    or
//    2) ./pthread nosleep 2> /tmp/error.log   # No sleep is done when each thread acquires pthread_mutex_lock
//       ==> We can find that both stdout and /tmp/error.log increase.

int main(int argc, char *argv[]) {
          if ((argc == 2) && (strcmp(argv[1], "nosleep") == 0))
                  {
                          sleepFlag = 0;
                  }
    pthread_t t[THREAD_NUM];

    int i;
    for (i = 0; i < THREAD_NUM; i++) {
      data[i] = i;
      int ret = pthread_create(&t[i], NULL, threadFunc, &data[i]);
      if (ret != 0) {
        perror("pthread_create error\n");
        exit(-1);
      }
    }   

    for (i = 0; i < THREAD_NUM; i++) {
      int ret = pthread_join(t[i], (void*)0);
      if (ret != 0) {
        perror("pthread_join error\n");
        exit(-1);
      }
    }

    exit(0);
}

Это вывод:

На терминале, где запущена программа:

    root@skyscribe:~# ./pthread sleep 2> /tmp/error.log

    [0]Before pthread_mutex_lock is called
    [0]pthread_mutex_lock is finisheded. Sleep some time
    [0]sleep done

    [0]Before pthread_mutex_unlock is called
    [0]pthread_mutex_unlock is finisheded.
    ...

На другом терминале посмотреть файл /tmp/error.log

    root@skyscribe:~# tail -f /tmp/error.log 

    [1]Before pthread_mutex_lock is called

И никакие новые строки не выводятся из /tmp/error.log

Ответы [ 2 ]

5 голосов
/ 11 ноября 2011

Это неправильный способ использования мьютексов. Поток не должен удерживать мьютекс дольше, чем ему не принадлежит, особенно если он не спит, удерживая мьютекс. Для блокировки мьютекса гарантии FIFO отсутствуют (по соображениям эффективности).

В частности, если поток 1 разблокирует мьютекс, пока поток 2 ожидает его, он делает поток 2 работоспособным, но это не заставляет планировщик выгружать поток 1 или запускать поток 2 немедленно. Скорее всего, этого не произойдет, потому что поток 1 недавно спал. Когда поток 1 впоследствии достигает вызова pthread_mutex_lock(), обычно разрешается немедленно блокировать мьютекс, даже если поток ожидает (и реализация может это знать). Когда поток 2 проснется после этого, он обнаружит, что мьютекс уже заблокирован и вернется в режим сна.

Лучшее решение - не удерживать мьютекс так долго. Если это невозможно, рассмотрите возможность перемещения операций, требующих блокировки, в один поток (устраняя необходимость в блокировке) или пробуждение правильного потока с помощью условных переменных.

1 голос
/ 11 ноября 2011

В вашем коде нет ни проблемы, ни ошибки, а комбинация эффектов буферизации и планирования. Добавьте fflush здесь:

 fprintf (fd, "[%d]pthread_mutex_unlock is finisheded.\n", *idx);
 fflush (fd);

и запустить

./a.out >1 1.log 2> 2.log &

и вы увидите довольно равный прогресс, достигнутый двумя потоками.

РЕДАКТИРОВАТЬ : и, как сказано выше @jilles, мьютекс должен быть коротким ожиданием блокировкой, а не долгим ожиданием как условная переменная ожидания , ожидая ввода-вывода или спать. Поэтому мьютекс тоже не точка отмены.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...