pthread_mutex_unlock не атомарный - PullRequest
       6

pthread_mutex_unlock не атомарный

1 голос
/ 08 декабря 2010

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

#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;
}

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

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

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

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

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

Michael

Ответы [ 4 ]

1 голос
/ 08 декабря 2010

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

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

1 голос
/ 08 декабря 2010

Ваш printf ("x") является примером состояния гонки в учебнике.

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

0 голосов
/ 09 декабря 2010

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

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

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

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

0 голосов
/ 09 декабря 2010

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

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

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

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

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

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

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

ИзЛюбопытно, что при отладке состояния потока, который зависает в pthread_mutex_unlock() / SetEvent(), есть ли у вас какая-либо информация о том, что именно происходит?Чего ждет * 1037?(отладчик 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.
*/
...