Взаимоблокировка EnterCriticalSection

Я нашел некоторый код, который утверждал, что может заставить поток спать в течение точного времени. Тестирование кода, кажется, работает отлично, однако он всегда блокируется после короткого промежутка времени.

Вот исходный код. Я ставил отпечатки перед входом и выходом из критической секции, и видел, что она иногда выходит или входит два раза подряд. Кажется, что он заходит в тупик при вызове EnterCriticalSection в функции ожидания.

Есть ли способ изменить этот код, чтобы сохранить его функциональность, не блокируя?

//----------------------------------------------------------------
class PreciseTimer
{
public:
   PreciseTimer() : mRes(0), toLeave(false), stopCounter(-1)
   {
      InitializeCriticalSection(&crit);
      mRes = timeSetEvent(1, 0, &TimerProc, (DWORD)this,
                          TIME_PERIODIC);
   }
   virtual ~PreciseTimer()
   {
      mRes = timeKillEvent(mRes);
      DeleteCriticalSection(&crit);
   }

   ///////////////////////////////////////////////////////////////
   // Function name   : Wait
   // Description     : Waits for the required duration of msecs.
   //                 : Timer resolution is precisely 1 msec
   // Return type     : void  :
   // Argument        : int timeout : timeout in msecs
   ///////////////////////////////////////////////////////////////
   void Wait(int timeout)
   {
      if ( timeout )
      {
         stopCounter = timeout;
         toLeave = true;
         // this will do the actual delay - timer callback shares
         // same crit section
         EnterCriticalSection(&crit);
         LeaveCriticalSection(&crit);
      }
   }
   ///////////////////////////////////////////////////////////////
   // Function name   : TimerProc
   // Description     : Timer callback procedure that is called
   //                 : every 1msec
   //                 : by high resolution media timers
   // Return type     : void CALLBACK  :
   // Argument        : UINT uiID :
   // Argument        : UINT uiMsg :
   // Argument        : DWORD dwUser :
   // Argument        : DWORD dw1 :
   // Argument        : DWORD dw2 :
   ///////////////////////////////////////////////////////////////
   static void CALLBACK TimerProc(UINT uiID, UINT uiMsg, DWORD
                                  dwUser, DWORD dw1, DWORD dw2)
   {
      static volatile bool entered = false;

      PreciseTimer* pThis = (PreciseTimer*)dwUser;
      if ( pThis )
      {
         if ( !entered && !pThis->toLeave )   // block section as
                                              // soon as we can
         {
            entered = true;
            EnterCriticalSection(&pThis->crit);
         }
         else if ( pThis->toLeave && pThis->stopCounter == 0 )
                                              // leave section
                                              // when counter
                                              // has expired
         {
            pThis->toLeave = false;
            entered = false;
            LeaveCriticalSection(&pThis->crit);
         }
         else if ( pThis->stopCounter > 0 )   // if counter is set
                                              // to anything, then
                                              // continue to drop
                                              // it...
            --pThis->stopCounter;
      }
   }

private:
   MMRESULT         mRes;
   CRITICAL_SECTION crit;
   volatile bool    toLeave;
   volatile int     stopCounter;
};


person user3124047    schedule 18.01.2014    source источник
comment
Как вы используете это, чтобы вызвать взаимоблокировку?   -  person Chad    schedule 18.01.2014
comment
Извините, единственная функция в этом классе — это Wait, и ее вызов вызывает взаимоблокировку через короткий случайный промежуток времени.   -  person user3124047    schedule 18.01.2014
comment
Но для вызова Wait() у вас должен быть объект. Как вы создаете этот объект? Покажите свою функцию main.   -  person Chad    schedule 18.01.2014
comment
После выхода из CS, если обратный вызов таймера вызывается снова до того, как ожидающий поток сможет войти в CS — 1 мс — это довольно маленькое окно — таймер снова войдет в CS, и программа заблокируется.   -  person Casey    schedule 18.01.2014
comment
Как показывает ответ Реми, для pThis->stopCounter нет защиты. Таким образом, если ЦП по какой-либо причине занят и TimerProc выполняется до завершения предыдущего вызова TimerProc, --pThis->stopCounter может выполняться дважды, и pThis->stopCounter становится отрицательным числом, а pThis->toLeave — истинным. Тогда LeaveCriticalSection никогда не вызывается.   -  person Yongwei Wu    schedule 18.01.2014


Ответы (1)


Тупик в EnterCriticalSection() обычно означает, что другой поток вызвал EnterCriticalSection(), но никогда не вызывал LeaveCriticalSection().

Как показано, этот код не очень потокобезопасен (а timeSetEvent() — это многопоточный таймер). Если несколько таймеров PreciseTimer работают одновременно, они используют один и тот же обратный вызов TimerProc() и, таким образом, совместно используют одну и ту же переменную entered, не защищая ее от одновременного доступа. И если несколько потоков одновременно вызывают Wait() для одного и того же объекта PreciseTimer, они перешагивают использование друг другом членов stopCounter и toLeave, которые также не защищены от одновременного доступа. Даже один поток, вызывающий Wait() для одного PreciseTimer, небезопасен, поскольку TimerProc() выполняется в своем собственном потоке, а stopCounter не защищен должным образом.

Этот код полон условий гонки.

person Remy Lebeau    schedule 18.01.2014
comment
Хороший ответ. Код действительно беспорядок. Я также не вижу гарантии выполнения EnterCriticalSection в Wait после вызова EnterCriticalSection в TimerProc. Так что ожидание может вообще не состояться. Точность настройки таймера также может сильно разряжать батарею. - person Yongwei Wu; 18.01.2014