VC ++ 2010: странная критическая ошибка раздела - PullRequest
5 голосов
/ 19 августа 2011

Моя программа случайно падает в небольшом сценарии, который я могу воспроизвести, но это происходит в mlock.c (это файл времени выполнения VC ++) из ntdll.dll, и я не вижу трассировки стека. Однако я знаю, что это происходит в одной из моих потоковых функций.

Это код mlock.c, в котором происходит сбой программы:

void __cdecl _unlock (
        int locknum
        )
{
        /*
         * leave the critical section.
         */
        LeaveCriticalSection( _locktable[locknum].lock );
}

Ошибка «указан неверный дескриптор». Если я посмотрю на locknum, это число больше размера _locktable, так что в этом есть какой-то смысл.

Похоже, это связано с использованием критического раздела. Я использую CRITICAL_SECTIONS в своем потоке через класс-оболочку CCriticalSection и связанный с ним RAII guard CGuard. Определения для здесь , чтобы избежать еще большего беспорядка.

Это функция потока, которая вылетает:

unsigned int __stdcall CPlayBack::timerThread( void * pParams ) {
#ifdef _DEBUG
    DRA::CommonCpp::SetThreadName( -1, "CPlayBack::timerThread" );
#endif
    CPlayBack * pThis = static_cast<CPlayBack*>( pParams );
    bool bContinue = true;
    while( bContinue ) {
        float m_fActualFrameRate = pThis->m_fFrameRate * pThis->m_fFrameRateMultiplier;
        if( m_fActualFrameRate != 0 && pThis->m_bIsPlaying ) {
            bContinue = ( ::WaitForSingleObject( pThis->m_hEndThreadEvent, static_cast<DWORD>( 1000.0f / m_fActualFrameRate ) ) == WAIT_TIMEOUT );
            CImage img;
            if( pThis->m_bIsPlaying && pThis->nextFrame( img ) )
                pThis->sendImage( img );
        }
        else
            bContinue = ( ::WaitForSingleObject( pThis->m_hEndThreadEvent, 10 ) == WAIT_TIMEOUT );
    }
    ::GetErrorLoggerInstance()->Log( LOG_TYPE_NOTE, "CPlayBack", "timerThread", "Exiting thread" );
    return 0;
}

Где CCriticalSection входит? Каждый объект CImage содержит объект CCriticalSection, который он использует через блокировку CGuard RAII. Более того, каждый CImage содержит CSharedMemory объект, который реализует подсчет ссылок. Для этого он также содержит два CCriticalSection, один для данных и один для счетчика ссылок. Хороший пример этих взаимодействий лучше всего виден в деструкторах:

CImage::~CImage() {
    CGuard guard(m_csData);
    if( m_pSharedMemory != NULL ) {
        m_pSharedMemory->decrementUse();
        if( !m_pSharedMemory->isBeingUsed() ){
            delete m_pSharedMemory;
            m_pSharedMemory = NULL;
        }
    }
    m_cProperties.ClearMin();
    m_cProperties.ClearMax();
    m_cProperties.ClearMode();
}

CSharedMemory::~CSharedMemory() {
    CGuard guardUse( m_cs );
    if( m_pData && m_bCanDelete ){
        delete []m_pData;
    }
    m_use = 0;
    m_pData = NULL;
}

Кто-нибудь сталкивался с такой ошибкой? Любое предложение?

Редактировать : Я увидел какой-то стек вызовов: вызов поступил из ~ CSharedMemory. Так что там должно быть какое-то состояние гонки

Редактировать : Подробнее код CSharedMemory здесь

Ответы [ 3 ]

5 голосов
/ 19 августа 2011

Код возврата «указан неверный дескриптор» рисует довольно четкую картину того, что ваш объект критической секции был освобожден;при условии, конечно, что он был выделен правильно с самого начала.

Ваш класс RAII кажется вероятным виновником.Если вы сделаете шаг назад и подумаете об этом, ваш класс RAII нарушает принцип Sepration of Concerns , потому что у него есть два задания:

  1. Он обеспечивает семантику выделения / уничтожения дляCRITICAL_SECTION
  2. Предоставляет семантику получения / выпуска для CRITICAL_SECTION

Большинство реализаций оболочки CS, которые я видел, аналогичным образом нарушают принцип SoC, но это может быть проблематично.Особенно, когда вам нужно начать передавать экземпляры класса, чтобы получить функциональность получения / выпуска.Рассмотрим простой надуманный пример в псевдокоде:

void WorkerThreadProc(CCriticalSection cs)
{
  cs.Enter();
  // MAGIC HAPPENS
  cs.Leave();
}

int main()
{
  CCriticalSection my_cs;
  std::vector<NeatStuff> stuff_used_by_multiple_threads;

  // Create 3 threads, passing the entry point "WorkerThreadProc"
  for( int i = 0; i < 3; ++i )
    CreateThread(... &WorkerThreadProc, my_cs);

  // Join the 3 threads...
  wait(); 
}

Проблема в том, что CCriticalSection передается по значению, поэтому деструктор вызывается 4 раза.Каждый раз, когда вызывается деструктор, CRITICAL_SECTION освобождается.Первое время работает хорошо, но теперь оно прошло.

Вы можете обойти эту проблему, передавая ссылки или указатели классу критического раздела, но затем вы запутываете смысловые воды проблемами владения.Что если поток, которому «принадлежит» крит, умирает раньше других потоков?Вы можете использовать shared_ptr, но теперь никто действительно не «владеет» критическим разделом, и вы отказались от небольшого контроля над областью, чтобы получить немного в другой области.

Истинное «исправление»«Для этого проблема состоит в том, чтобы разделить проблемы.Есть один класс для выделения и освобождения:

class CCriticalSection : public CRITICAL_SECTION
{
public:
  CCriticalSection(){ InitializeCriticalSection(this); }
  ~CCriticalSection() { DestroyCriticalSection(this); }
};

... и другой для обработки блокировки и разблокировки ...

class CSLock
{
public:
  CSLock(CRITICAL_SECTION& cs) : cs_(cs) { EnterCriticalSection(&cs_); }
  ~CSLock() { LeaveCriticalSection(&cs_); }
private: 
  CRITICAL_SECTION& cs_;
};

Теперь вы можете передавать необработанные указатели или ссылки наодин объект CCriticalSection, возможно const, и рабочие потоки создают на нем свои собственные CSLocks.CSLock принадлежит потоку, который его создал, что и должно быть, но владение CCriticalSection явно сохраняется некоторым управляющим потоком;тоже хорошая вещь.

1 голос
/ 25 августа 2011

Я решил придерживаться принципа KISS, и рок-н-ролл все 100 * упрощают вещи.Я решил заменить CSharedMemoryClass на std::tr1::shared_ptr<BYTE> и CCriticalSection, что защищает его от одновременного доступа.Теперь они оба являются членами CImage, и теперь проблемы лучше разделены, ИМХО.

Это решило странный критический раздел, но теперь, похоже, у меня есть утечка памяти, вызванная std::tr1::shared_ptr, вы можете увидеть меняскоро напишу об этом ... Это никогда не заканчивается!

1 голос
/ 19 августа 2011
  • Убедитесь, что объект критического сечения не находится в #pragma упаковке 1 (или в любой упаковке не по умолчанию).
  • Убедитесь, что никакой другой поток (или тот же поток) не портит объект CS. Запустите инструмент статического анализа, чтобы проверить наличие проблем с переполнением буфера.
  • Если у вас есть инструмент анализа времени выполнения, запустите его, чтобы найти проблему.
...