VC ++ 2010: erreur de section critique étrange

Mon programme plante aléatoirement dans un petit scénario que je peux reproduire, mais cela se produit dans mlock.c (qui est un fichier d’exécution VC ++) de ntdll.dll, et je ne peux pas voir la trace de la stack. Je sais que cela se produit dans une de mes fonctions de fil, cependant.

C’est le code mlock.c où le programme se bloque:

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

L’erreur est “handle non valide spécifié”. Si je regarde locknum, c’est un nombre plus grand que la taille de _locktable, donc cela a du sens.

Cela semble être lié à l’utilisation de la section critique. J’utilise CRITICAL_SECTIONS dans mon thread, via une classe de wrapper CCriticalSection et son RAII guard associé, CGuard. Définitions pour les deux ici pour éviter encore plus d’encombrement.

C’est la fonction de thread qui se bloque:

 unsigned int __stdcall CPlayBack::timerThread( void * pParams ) { #ifdef _DEBUG DRA::CommonCpp::SetThreadName( -1, "CPlayBack::timerThread" ); #endif CPlayBack * pThis = static_cast( 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( 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 entre-t-il? Chaque object CImage contient un object CCriticalSection qu’il utilise via un verrou CGuard RAII. De plus, chaque CImage contient un object CSharedMemory qui implémente le comptage de références. Pour cela, il contient également deux CCriticalSection , une pour les données et une pour le compteur de référence. Un bon exemple de ces interactions se voit mieux dans les destructeurs:

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

Quelqu’un a-t-il rencontré ce genre d’erreur? Toute suggestion?

Edit : Je dois voir une stack d’appels: l’appel provient de ~ CSharedMemory. Donc, il doit y avoir une condition de course là-bas

Edit : Plus de code CSharedMemory ici

Le code retour “handle non valide spécifié” donne une image assez claire du fait que votre object de section critique a été désalloué; en supposant bien sûr qu’il a été alloué correctement pour commencer.

Votre classe RAII semble être un coupable probable. Si vous prenez du recul et que vous y réfléchissez, votre classe RAII viole le principe de Sepration Of Concerns , car elle a deux emplois:

  1. Il fournit l’allocation / destruction de la sémantique pour CRITICAL_SECTION
  2. Il fournit une sémantique d’acquisition / libération pour CRITICAL_SECTION

La plupart des implémentations d’un wrapper CS que j’ai vu violent le principe SoC de la même manière, mais cela peut être problématique. Surtout lorsque vous devez commencer à faire circuler des instances de la classe pour accéder à la fonctionnalité d’acquisition / libération. Considérons un exemple simple et inventé en psudocode:

 void WorkerThreadProc(CCriticalSection cs) { cs.Enter(); // MAGIC HAPPENS cs.Leave(); } int main() { CCriticalSection my_cs; std::vector 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(); } 

Le problème ici est que CCriticalSection est passé par valeur, donc le destructeur est appelé 4 fois. Chaque fois que le destructeur est appelé, le CRITICAL_SECTION est désalloué. La première fois marche bien, mais maintenant c'est parti.

Vous pouvez contourner ce problème en transmettant des références ou des pointeurs à la classe de la section critique, mais vous brouillez les eaux sémantiques avec les problèmes de propriété. Que se passe-t-il si le thread qui "possède" la critique meurt avant les autres threads? Vous pourriez utiliser un shared_ptr , mais maintenant, personne ne "possède" vraiment la section critique, et vous avez abandonné un peu de contrôle sur la zone afin de gagner un peu dans un autre domaine.

Le véritable "correctif" pour ce problème est de séparer les préoccupations. Avoir une classe pour l'allocation et la désallocation:

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

... et un autre pour gérer le locking et le délocking ...

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

Vous pouvez maintenant passer des pointeurs bruts ou des références à un seul object CCriticalSection, éventuellement const, et faire en sorte que les threads de travail instancient leurs propres CSLocks. Le CSLock appartient au thread qui l'a créé, ce qui est le cas, mais la propriété de CCriticalSection est clairement conservée par un thread de contrôle; aussi une bonne chose.

  • Assurez-vous que l’object Critical Section n’est pas dans #pragma packing 1 (ou un emballage autre que celui par défaut).
  • Assurez-vous qu’aucun autre thread (ou même thread) ne corrompe l’object CS. Exécutez un outil d’parsing statique pour vérifier tout problème de saturation de la mémoire tampon.
  • Si vous disposez d’un outil d’parsing d’exécution, exécutez-le pour trouver le problème.

J’ai décidé d’adhérer au principe KISS et rock and roll toute la nuit simplifier les choses. J’ai pensé que je remplacerais le CSharedMemoryClass avec un std::tr1::shared_ptr et un CCriticalSection qui le protège des access concurrents. Les deux sont membres du CImage maintenant, et les préoccupations sont mieux séparées maintenant, à CImage .

Cela a résolu la section critique étrange, mais maintenant, il semble que j’ai une fuite de mémoire causée par std::tr1::shared_ptr , vous pourriez me voir poster à ce sujet bientôt … Il ne se termine jamais!