2010-12-08 2 views
1

Я следующий исходный код (адаптировано из моего оригинального кода):pthread_mutex_unlock не атомное

#include "stdafx.h" 
#include <stdlib.h> 
#include <stdio.h> 

#include "pthread.h" 

#define MAX_ENTRY_COUNT 4 

int entries = 0; 
bool start = false; 

bool send_active = false; 

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 
pthread_cond_t condNotEmpty = PTHREAD_COND_INITIALIZER; 
pthread_cond_t condNotFull = PTHREAD_COND_INITIALIZER; 

void send() 
{ 
    for (;;) { 
     if (!start) 
      continue; 
     start = false; 

     for(int i = 0; i < 11; ++i) { 
      send_active = true; 

      pthread_mutex_lock(&mutex); 
      while(entries == MAX_ENTRY_COUNT) 
       pthread_cond_wait(&condNotFull, &mutex);  
      entries++; 
      pthread_cond_broadcast(&condNotEmpty); 
      pthread_mutex_unlock(&mutex); 

      send_active = false; 
     } 
    } 
} 

void receive(){ 
    for(int i = 0; i < 11; ++i){ 
     pthread_mutex_lock(&mutex); 
     while(entries == 0) 
      pthread_cond_wait(&condNotEmpty, &mutex); 
     entries--; 
     pthread_cond_broadcast(&condNotFull); 
     pthread_mutex_unlock(&mutex); 
    } 

    if (send_active) 
     printf("x"); 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    pthread_t s; 

    pthread_create(&s, NULL, (void *(*)(void*))send, NULL); 

    for (;;) { 
     pthread_mutex_init(&mutex, NULL); 
     pthread_cond_init(&condNotEmpty, NULL); 
     pthread_cond_init(&condNotFull, NULL); 

     start = true; 

     receive(); 

     pthread_mutex_destroy(&mutex); 
     mutex = NULL; 
     pthread_cond_destroy(&condNotEmpty); 
     pthread_cond_destroy(&condNotFull); 
     condNotEmpty = NULL; 
     condNotFull = NULL; 

     printf("."); 
    } 

    return 0; 
} 

Проблема заключается в том, как следует: время от времени последней разблокировки в функции отправки не закончена до метод приема продолжается. В моем исходном коде мьютексы расположены в объектах, которые удаляются после выполнения задания. Если метод отправки не завершился с последней разблокировкой, мьютексы недействительны, и моя программа вызывает сбои в разблокировке.

Поведение может быть легко воспроизведено путем запуска программы: каждый раз, когда «x» дублируется, метод приема почти завершен, и метод отправки «зависает» в вызове разблокировки.

Я скомпилирован с VS2008 и VS2010 - оба результата одинаковы.

pthread_mutex_unlock не является атомарным, это решит проблему. Как я могу решить эту проблему? Любые комментарии приветствуются ...

С наилучшими пожеланиями

Майкл

+0

Я не уверен, что полностью понимаю проблему, но вам не нужно ждать окончания потока отправки (`pthread_join (s, NULL);`), прежде чем пытаться уничтожить мьютекс, на который он опирается ? – 2010-12-08 12:57:20

+0

Также вам не нужно/не следует инициализировать мьютексы как со статическим инициализатором `PTHREAD_MUTEX_INITIALIZER`, так и с вызовом` pthread_mutex_init`. (Попытка инициализировать уже инициализированный мьютекс приводит к неопределенному поведению.) – 2010-12-08 13:01:38

+0

Посылающий поток - это поток, который никогда не заканчивается в моем приложении (фактически это поток ввода-вывода). – michael 2010-12-08 13:12:02

ответ

1

Вашего Е («х») является хрестоматийным примером состояния гонки.

После того как pthread_mutex_unlock() OS может не планировать этот поток за любое количество времени: тики, секунды или дни. Вы не можете предположить, что send_active будет «фальсифицирован» вовремя.

1

pthread_mutex_unlock() должен по определению освободить мьютекс до его возвращения. В момент освобождения мьютекса может быть запланирован другой поток, который поддерживает мьютекс. Обратите внимание, что даже если pthread_mutex_unlock() может организовать передачу мьютекса до тех пор, пока он не вернется (что, я думаю, вы подразумеваете под его атомарным), все равно будет эквивалентное условие гонки для того, что вы видите сейчас (мне не ясно какая гонка вы видите, так как комментарий указывает, что не заинтересованы в состоянии гонки при доступе к send_active для управления вызовом printf()).

В этом случае другой поток может быть запланирован «между строками» pthread_mutex_unlock() и следующий оператор/выражение в вызываемой ему функции - у вас будет такое же состояние гонки.

0

Вот некоторые предположения о том, что может произойти. Пара предостережений на этот анализ:

  • это основано на предположении, что вы используете PThreads пакет Win32 из http://sourceware.org/pthreads-win32/
  • это основано только на довольно кратком рассмотрении источника PThreads на этом сайте и информация в вопросе и комментариях здесь - у меня не было возможности на самом деле попытаться запустить/отладить любой код.

Когда pthread_mutex_unlock() называется, он уменьшает счетчик блокировок, и если счетчик блокировок падает до нуля, Win32 API SetEvent() вызывается на соответствующий объект события, чтобы любые темы, ожидающие мьютекс быть разблокирован. Довольно стандартный материал.

Здесь есть предположение. Предположим, что SetEvent() был вызван, чтобы разблокировать поток (ы), ожидающий мьютекс, и он сигнализирует событие, связанное с дескриптором, которым оно было дано (как и должно быть).Однако перед тем как функция SetEvent() выполняет что-то еще, другой поток запускается и закрывает дескриптор объекта события, к которому был вызван точный SetEvent() (путем вызова pthread_mutex_destroy()).

У текущего SetEvent() у текущего вызова значение дескриптора больше недействительно. Я не могу придумать какую-то конкретную причину, по которой SetEvent() будет делать что-либо с этим дескриптором после того, как он сигнализирует об этом событии, но, возможно, это так (я также мог представить, что кто-то делает разумный аргумент, что SetEvent() должен уметь ожидать, что дескриптор объекта события останется действительный на время вызова API).

Если это то, что происходит с вами (и это big if), я не уверен, есть ли легкое исправление. Я думаю, что библиотека pthreads должна была бы внести изменения, чтобы она дублировала дескриптор события перед вызовом SetEvent(), затем закройте этот дубликат при возврате вызова SetEvent(). Таким образом, дескриптор останется действительным, даже если «главный» дескриптор был закрыт другим потоком. Я предполагаю, что это должно было бы сделать это во многих местах. Это может быть реализовано путем замены затронутых вызовов API Win32 вызовами функций-оболочек, которые выполняют последовательность дубликатов дескриптора/вызова API/закрытия.

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

Из любопытства, при отладке состояния нити, которая висит в pthread_mutex_unlock()/SetEvent(), есть ли у вас какая-либо информация о том, что именно происходит? Что такое SetEvent()? (отладчик cdb, находящийся в пакете «Отладка инструментов для Windows», может предоставить вам больше информации об этом, чем отладчик Visual Studio).

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

/* 
* FIXME!!! 
* The mutex isn't held by another thread but we could still 
* be too late invalidating the mutex below since another thread 
* may already have entered mutex_lock and the check for a valid 
* *mutex != NULL. 
* 
* Note that this would be an unusual situation because it is not 
* common that mutexes are destroyed while they are still in 
* use by other threads. 
*/ 
0

Майкла, спасибо за ваше расследование и комментарии!

Код, который я использую, от http://sourceware.org/pthreads-win32/.

Ситуация, описанная вами в третьем и четвертом абзацах, является именно тем, что происходит.

Я проверил некоторые решения, и для меня, похоже, работает простой: я ожидаю завершения функции отправки (и SetEvent). До сих пор все мои тесты с этим решением были успешными. В выходные я собираюсь сделать более крупный тест.