2011-08-19 12 views
5

Il mio programma si blocca in modo casuale in un piccolo scenario che posso riprodurre, ma succede in mlock.c (che è un file di runtime VC++) da ntdll.dll, e non riesco a vedere il traccia dello stack. So che succede in una delle mie funzioni thread, però.VC++ 2010: errore di sezione critica strano

Questo è il codice mlock.c in cui il programma si blocca:

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

L'errore è "handle non valido specificato". Se guardo il locknum, è un numero più grande della dimensione di _locktable, quindi questo ha un senso.

Questo sembra essere correlato all'utilizzo della sezione critica. Uso CRITICAL_SECTIONS nella mia discussione, tramite una classe wrapper CCriticalSection e la relativa protezione RAII associata, CGuard. Definizioni per entrambi here per evitare ancora più confusione.

Questa è la funzione di filo che è schiantarsi:

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

dove viene CCriticalSection entrare? Ogni oggetto CImage contiene un oggetto CCriticalSection che utilizza tramite un blocco RAII CGuard. Inoltre, ogni CImage contiene un oggetto CSharedMemory che implementa il conteggio dei riferimenti. A tal fine, contiene anche due CCriticalSection, uno per i dati e uno per il contatore di riferimento. Un buon esempio di queste interazioni è visto meglio nelle distruttori:

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

Chiunque urtato questo tipo di errore? Qualche suggerimento?

Modifica: Ho visto alcuni stack di chiamate: la chiamata proviene da ~ CSharedMemory. Quindi ci deve essere una certa condizione di competizione ci

Edit: codice più CSharedMemory here

+0

Corruzione della memoria? –

+0

Le due classi stesse stanno bene. Puoi mostrare qualche codice relativo a COME li stai usando? Sei sicuro che i costruttori siano chiamati correttamente prima dell'uso (nessuna contesa sul thread sui costruttori?). Sono assegnati dinamicamente (per qualche motivo)? – Chad

+0

La tua classe non ha nulla a che fare con il codice CRT, usa Windows. Il debugging delle gare di threading e della corruzione dell'heap non è mai divertente, buona fortuna. –

risposta

1

ho deciso di aderire al principio KISS e rock and roll all nite semplificare le cose. Ho pensato di sostituire lo CSharedMemoryClass con uno std::tr1::shared_ptr<BYTE> e uno CCriticalSection che lo protegge dall'accesso simultaneo. Entrambi sono membri di CImage adesso e le preoccupazioni ora sono meglio separate, IMHO.

Questo ha risolto la sezione critica strana, ma ora sembra che io abbia una perdita di memoria causata da std::tr1::shared_ptr, potresti vedermi postare a breve ... Non finisce mai!

5

Il "non valido handle specificato" codice di ritorno dipinge un quadro abbastanza chiaro che l'oggetto sezione critica è stato deallocato; presupponendo naturalmente che sia stato assegnato correttamente per cominciare.

La tua classe RAII sembra un probabile colpevole. Se si prende un passo indietro e pensare a questo proposito, la classe RAII viola il principio Sepration Of Concerns, perché ha due lavori:

  1. Fornisce allocare/distruggere la semantica per la CRITICAL_SECTION
  2. Fornisce la semantica acquisire/rilascio per la CRITICAL_SECTION

La maggior parte delle implementazioni di un wrapper CS che ho visto violano il principio SoC allo stesso modo, ma può essere problematico. Soprattutto quando devi iniziare a passare le istanze della classe per arrivare alla funzionalità di acquisizione/rilascio. Si consideri un semplice esempio forzato in psudocode:

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

Il problema qui è CCriticalSection viene passato per valore, per cui il distruttore viene chiamato 4 volte. Ogni volta che viene chiamato il distruttore, la CRITICAL_SECTION viene deallocata. La prima volta funziona bene, ma ora non c'è più.

È possibile aggirare questo problema passando i riferimenti o i puntatori alla classe della sezione critica, ma poi si confondono le acque semantiche con problemi di proprietà. Cosa succederebbe se il thread che "possiede" il critico fosse morto prima degli altri thread? Potresti usare uno shared_ptr, ma nessuno ha mai realmente "posseduto" la sezione critica, e hai rinunciato a un piccolo controllo sull'area per guadagnare un po 'in un'altra area.

La vera "correzione" per questo problema è di separare le preoccupazioni. Avere una classe per l'assegnazione & deallocazione:

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

... e un altro per gestire il blocco & sblocco ...

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

Ora è possibile passare in giro puntatori prime o riferimenti a un singolo oggetto CCriticalSection, possibilmente const, e avere i thread di lavoro istanziare i propri CSLocks su di esso. CSLock è di proprietà del thread che lo ha creato, che è come dovrebbe essere, ma la proprietà di CCriticalSection è chiaramente mantenuta da alcuni thread di controllo; anche una buona cosa.

+0

Non vedo come questo differisce da quello che ho fatto prima, eccetto che usa ereditarietà invece di composizione e chiama EnterCriticalSection e LeaveCriticalSection nell'equivalente alla mia classe CGuard . Cosa guadagno dal farlo in quel modo? –

+0

Hai cambiato la tua domanda al punto che ora non sappiamo come si presenta CCriticalSection, che cos'è CGuard o come sono collegati. Dici cose come "Ogni oggetto CImage contiene un oggetto CCriticalSection che usa attraverso un blocco di CGuard RAII" ma a meno che non vediamo come vengono dichiarati e implementati, non sappiamo veramente cosa significhi. –

+0

Scusa, ho rimosso quel codice perché qualcuno mi ha detto che le mie lezioni erano a posto e per evitare di ingombrare la domanda. L'ho pubblicato su Ideone.com, vedere il collegamento nella mia domanda aggiornata –

1
  • Assicurarsi che l'oggetto della sezione critica non si trovi in ​​#pragma imballaggio 1 (o qualsiasi imballaggio non predefinito).
  • Verificare che nessun altro thread (o lo stesso thread) stia corrompendo l'oggetto CS. Esegui alcuni strumenti di analisi statica per verificare eventuali problemi di sovraccarico del buffer.
  • Se si dispone di uno strumento di analisi runtime, eseguirlo per individuare il problema.
Problemi correlati