EnterCriticalSection Deadlock - PullRequest
       0

EnterCriticalSection Deadlock

3 голосов
/ 18 февраля 2011

Имея то, что кажется тупиковой ситуацией с многопоточным приложением регистрации.

Маленький фон:

В моем основном приложении запущено 4-6 потоков. Основной поток, отвечающий за мониторинг работоспособности различных вещей, которые я делаю, обновление графического интерфейса и т. Д. Затем у меня есть поток передачи и поток приема. Передающие и принимающие потоки взаимодействуют с физическим оборудованием. Иногда мне нужно отладить данные, которые видят потоки передачи и получения; то есть печатать на консоль, не прерывая их из-за их критичного ко времени характера данных. Данные, кстати, находятся на шине USB.

Из-за многопоточности приложения я хочу создать консоль отладки, в которую я могу отправлять сообщения из других моих потоков. Получатель отладки работает как поток с низким приоритетом и реализует кольцевой буфер, так что при печати на консоль отладки сообщение быстро сохраняется в кольцевом буфере и устанавливает и событие. Поток консоли отладки хранит события WaitingOnSingleObject из входящих сообщений, которые входят в него. При обнаружении события поток консоли обновляет отображение графического интерфейса с помощью сообщения. Простой а? Печатные вызовы и поток консоли используют критическую секцию для управления доступом.

ПРИМЕЧАНИЕ. Я могу настроить размер кольцевого буфера, если увижу, что я сбрасываю сообщения (по крайней мере, это идея).

В тестовом приложении консоль работает очень хорошо, если я медленно вызываю ее метод Print с помощью щелчков мыши. У меня есть кнопка, которую я могу нажать для отправки сообщений на консоль, и она работает. Однако, если я добавлю какую-либо нагрузку (много вызовов метода Print), все будет заблокировано. Когда я отслеживаю взаимоблокировку, отладчик моей IDE отслеживает EnterCriticalSection и сидит там.

ПРИМЕЧАНИЕ. Если я удаляю вызовы Lock / UnLock и просто использую Enter / LeaveCriticalSection (см. Код), я иногда работаю, но все же оказываюсь в тупиковой ситуации. Чтобы исключить взаимные блокировки для стека push / pops, я прямо сейчас вызываю Enter / LeaveCriticalSection, но это не решило мою проблему .... Что здесь происходит?

Вот один оператор Print, который позволяет мне передать простой int на консоль дисплея.

void TGDB::Print(int I)
{
    //Lock();
    EnterCriticalSection(&CS);

    if( !SuppressOutput )
    {
        //swprintf( MsgRec->Msg, L"%d", I);
        sprintf( MsgRec->Msg, "%d", I);
        MBuffer->PutMsg(MsgRec, 1);
    }

    SetEvent( m_hEvent );
    LeaveCriticalSection(&CS);
    //UnLock();
}

// My Lock/UnLock methods
void TGDB::Lock(void)
{
    EnterCriticalSection(&CS);
}

bool TGDB::TryLock(void)
{
    return( TryEnterCriticalSection(&CS) );
}

void TGDB::UnLock(void)
{
        LeaveCriticalSection(&CS);
}

// This is how I implemented Console's thread routines

DWORD WINAPI TGDB::ConsoleThread(PVOID pA)
{
DWORD rVal;

         TGDB *g = (TGDB *)pA;
        return( g->ProcessMessages() );
}

DWORD TGDB::ProcessMessages()
{
DWORD rVal;
bool brVal;
int MsgCnt;

    do
    {
        rVal = WaitForMultipleObjects(1, &m_hEvent, true, iWaitTime);

        switch(rVal)
        {
            case WAIT_OBJECT_0:

                EnterCriticalSection(&CS);
                //Lock();

                if( KeepRunning )
                {
                    Info->Caption = "Rx";
                    Info->Refresh();
                    MsgCnt = MBuffer->GetMsgCount();

                    for(int i=0; i<MsgCnt; i++)
                    {
                        MBuffer->GetMsg( MsgRec, 1);
                        Log->Lines->Add(MsgRec->Msg);
                    }
                }

                brVal = KeepRunning;
                ResetEvent( m_hEvent );
                LeaveCriticalSection(&CS);
                //UnLock();

            break;

            case WAIT_TIMEOUT:
                EnterCriticalSection(&CS);
                //Lock();
                Info->Caption = "Idle";
                Info->Refresh();
                brVal = KeepRunning;
                ResetEvent( m_hEvent );
                LeaveCriticalSection(&CS);
                //UnLock();
            break;

            case WAIT_FAILED:
                EnterCriticalSection(&CS);
                //Lock();
                brVal = false;
                Info->Caption = "ERROR";
                Info->Refresh();
                aLine.sprintf("Console error: [%d]", GetLastError() );
                Log->Lines->Add(aLine);
                aLine = "";
                LeaveCriticalSection(&CS);
                //UnLock();
            break;
        }

    }while( brVal );

    return( rVal );
}

MyTest1 и MyTest2 - это всего лишь две тестовые функции, которые я вызываю в ответ на нажатие кнопки. MyTest1 никогда не вызывает проблем, независимо от того, насколько быстро я нажимаю кнопку. MyTest2 блокируется практически каждый раз.

// No Dead Lock
void TTest::MyTest1()
{
    if(gdb)
    {
        // else where: gdb = new TGDB;
        gdb->Print(++I);
    }
}


// Causes a Dead Lock
void TTest::MyTest2()
{
    if(gdb)
    {
        // else where: gdb = new TGDB;
        gdb->Print(++I);
        gdb->Print(++I);
        gdb->Print(++I);
        gdb->Print(++I);
        gdb->Print(++I);
        gdb->Print(++I);
        gdb->Print(++I);
        gdb->Print(++I);
    }
}

UPDATE: Нашел ошибку в моей реализации кольцевого буфера. Под большой нагрузкой, когда буфер был упакован, я не обнаружил полный буфер должным образом, поэтому буфер не возвращался. Я почти уверен, что проблема сейчас решена. Как только я исправил проблему с кольцевым буфером, производительность стала намного лучше. Однако, если я уменьшу значение iWaitTime, моя мертвая блокировка (или проблема с зависанием) вернется.

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

Кроме того, мой обновленный код указан выше. Я знаю, убедитесь, что мои вызовы событий Set & Reset находятся внутри вызовов критической секции.

Ответы [ 3 ]

4 голосов
/ 18 февраля 2011

С закрытыми опциями я бы задавал вопросы об этом объекте "Информация".Это окно, к какому окну оно относится, и для какого потока оно было создано?

Если Info или его родительское окно было создано в другом потоке, может возникнуть следующая ситуация:

Консольный поток находится внутри критической секции, обрабатывающей сообщение.Главный поток вызывает Print () и блокирует критическую секцию, ожидая, когда консольный поток снимет блокировку.Поток консоли вызывает функцию Info (Caption), в результате чего система отправляет сообщение (WM_SETTEXT) в окно.SendMessage блокируется, поскольку целевой поток не находится в состоянии оповещения о сообщении (не блокируется при вызове GetMessage / WaitMessage / MsgWaitForMultipleObjects).

Теперь у вас есть тупик.

Этот тип# $ (% ^ может произойти всякий раз, когда вы смешиваете блокирующие подпрограммы с чем-либо, что взаимодействует с окнами. Единственная подходящая блокирующая функция для использования в потоке графического интерфейса - это MSGWaitForMultipleObjects, в противном случае вызовы SendMessage для окон, размещенных в потоке, могут легко заблокироваться.

Чтобы избежать этого, необходимо два возможных подхода:

  • Никогда не выполнять никаких взаимодействий с графическим интерфейсом в рабочих потоках. Используйте только PostMessage для отправки неблокирующих команд обновления пользовательского интерфейса в поток пользовательского интерфейса, ИЛИ
  • Используйте ядроОбъекты событий + MSGWaitForMultipleObjects (в потоке графического интерфейса), чтобы гарантировать, что даже когда вы блокируете ресурс, вы все равно отправляете сообщения.
2 голосов
/ 18 февраля 2011

Я бы настоятельно рекомендовал реализацию без блокировки.

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

Я предлагаю проект на основе SList(Win32 API обеспечивает реализацию SList, но вы можете достаточно легко создать потокобезопасный шаблон, используя InterlockedCompareExchange и InterlockedExchange).Каждый поток будет иметь пул буферов.Каждый буфер будет отслеживать поток, из которого он пришел, после обработки буфера менеджер журналов отправит буфер обратно в SList исходного потока для повторного использования.Потоки, желающие написать сообщение, опубликуют буфер в потоке регистратора.Это также препятствует тому, чтобы любой поток истощил другие потоки буферов.Событие для пробуждения потока средства ведения журнала, когда буфер помещен в очередь, завершает проектирование.

2 голосов
/ 18 февраля 2011

Не зная, где находится тупик, этот код понять сложно.Два комментария:

  • Учитывая, что это c ++, вы должны использовать объект Auto для блокировки и разблокировки.На всякий случай, когда Log выдаст исключение, он станет не катастрофическим.

  • Вы сбрасываете событие в ответ на WAIT_TIMEOUT.Это оставляет небольшое окно возможности для второго вызова Print (), чтобы установить событие, пока рабочий поток вернулся из WaitForMultiple, но до того, как он вошел в критическую секцию.Это приведет к тому, что событие будет сброшено при наличии ожидающих данных.

Но вам нужно отладить его и указать, где он находится в «тупиках».Если один поток застрял на EnterCriticalSection, то мы можем выяснить, почему.Если нет ни одной нити, то неполная печать - это просто результат потерянного события.

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