c ++ - VC ++ 2010: Weird Critical Section fejl

Indlæg af Hanne Mølgaard Plasc

Problem



Mit program slår tilfældigt i et lille scenario, jeg kan reproducere, men det sker i mlock.c (hvilket er en VC ++ runtime fil) fra ntdll.dll, og jeg kan ikke se stakken spor. Jeg ved, at det sker i en af ​​mine trådfunktioner, dog.


Dette er mlock.c-koden, hvor programmet kolliderer:


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


Fejlen er 'ugyldigt håndteret specificeret'. Hvis jeg ser på locknum, er det et antal større end \_locktable s størrelse, så det giver mening.


Dette ser ud til at være relateret til brug af kritisk sektion. Jeg bruger CRITICAL\_SECTIONS i min tråd via en CCriticalSection wrapper klasse og tilhørende RAII guard, CGuard. Definitioner for begge her for at undgå endnu mere rod. [22]


Dette er den trådfunktion, som 's krasjer:


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


Hvor kommer CCriticalSection ind? Hver CImage -objekt indeholder et CCriticalSection objekt, som det bruger via en CGuard RAII-lås. Desuden indeholder hver CImage en CSharedMemory objekt, der udfører referencetælling. Til det formål indeholder den også to CCriticalSection s, en til dataene og en til referencetælleren. Et godt eksempel på disse interaktioner ses bedst i destruktorerne:


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


Nogen stødte på denne slags fejl? Nogle forslag?


Rediger : Jeg fik at se nogle call stack: opkaldet kommer fra ~ CSharedMemory. Så der skal være nogle løbstilstand der


Rediger : Mere CSharedMemory kode her [23]

Bedste reference


Den 'ugyldige håndbagespecifikke' returkode maler et ret klart billede, at dit kritiske sektionsobjekt er blevet allokeret; forudsat selvfølgelig at det blev tildelt korrekt til at begynde med.


Din RAII-klasse virker som en sandsynlig synder. Hvis du tager et skridt tilbage og tænker på det, overtræder din RAII-klasse princippet om indvielse af bekymringer, fordi det har to job: [24]



  1. Det giver tildele/ødelægge semantik til CRITICAL\_SECTION

  2. Det giver erhverve/frigiv semantik til CRITICAL\_SECTION



De fleste implementeringer af en CS-wrapper, jeg har set, overtræder SoC-princippet på samme måde, men det kan være problematisk. Især når du er nødt til at begynde at gå forbi klassenes tilfælde for at komme til erhvervet/frigivelsesfunktionen. Overvej et simpelt, konstrueret eksempel i psudokode:


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(); 
}


Problemet her er CCriticalSection går efter værdi, så destructoren hedder 4 gange. Hver gang destruktoren hedder, er CRITICAL\_SECTION allokeret. Første gang fungerer fint, men nu er det væk.


Du kan kludge omkring dette problem ved at sende referencer eller pointers til den kritiske sektionsklasse, men så mudder du de semantiske farvande med ejerskabsproblemer. Hvad hvis tråden, der 'ejer', drejer crit sek før de andre tråde? Du kan bruge en shared\_ptr, men nu ejer ingen 'den kritiske sektion', og du har givet lidt kontrol i området for at få lidt i et andet område.


Den sande 'løsning' for dette problem er at adskille bekymringer. Har en klasse til allokering & deallokering:


class CCriticalSection : public CRITICAL\_SECTION
{
public:
  CCriticalSection(){ InitializeCriticalSection(this); }
  ~CCriticalSection() { DestroyCriticalSection(this); }
};


... og en anden til at håndtere låsning & oplåsning ...


class CSLock
{
public:
  CSLock(CRITICAL\_SECTION& cs) : cs\_(cs) { EnterCriticalSection(&cs\_); }
  ~CSLock() { LeaveCriticalSection(&cs\_); }
private: 
  CRITICAL\_SECTION& cs\_;
};


Nu kan du videregive røde pointers eller referencer til et enkelt CCriticalSection-objekt, muligvis const, og få medarbejderens tråde at instantiere deres egne CSLocks på den. CSLock ejes af den tråd, der skabte den, hvilket er som det burde være, men ejerskabet af CCriticalSection er tydeligt bevaret af nogle styrende tråd; også en god ting.

Andre referencer 1



  • Sørg for, at Critical Section-objektet ikke er i #pragma pakning 1 (eller nogen anden standardpakning).

  • Sørg for, at ingen anden tråd (eller samme tråd) ødelægger CS-objektet. Kør nogle statiske analyseværktøjer for at kontrollere, om der er problemer med bufferoverskridelse.

  • Hvis du har et runtimeanalyseværktøj, skal du køre det for at finde problemet.


Andre referencer 2


Jeg besluttede at overholde KISS-princippet og rock og roll all nite forenkle tingene. Jeg regnede med at jeg skulle erstatte CSharedMemoryClass med en std::tr1::shared\_ptr<BYTE> og en CCriticalSection, som beskytter den mod samtidig adgang. Begge er medlemmer af CImage nu, og bekymringer er bedre adskilt nu , IMHO.


Det løste den underlige kritiske sektion, men nu ser det ud til, at jeg har en hukommelselækage forårsaget af std::tr1::shared\_ptr, du kan se mig skrive om det snart ... Det slutter aldrig!