5

私のプログラムは、再現できる小さなシナリオでランダムにクラッシュしますが、ntdll.dll の mlock.c (VC++ ランタイム ファイル) で発生し、スタック トレースが表示されません。ただし、スレッド関数の1つで発生することは知っています。

これは、プログラムがクラッシュする mlock.c コードです。

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

エラーは「無効なハンドルが指定されました」です。locknum を見ると、_locktable のサイズよりも大きい数値なので、これはある程度理にかなっています。

これは、クリティカル セクションの使用法に関連しているようです。CCriticalSection ラッパー クラスとそれに関連する RAII ガードである CGuard を介して、スレッドで CRITICAL_SECTIONS を使用しています。さらに混乱を避けるために、両方の定義をここに示します。

これはクラッシュしているスレッド関数です:

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くる?すべてのオブジェクトには、 RAII ロックを介して使用するCImageオブジェクトが含まれています。さらに、everyには、参照カウントを実装するオブジェクトが含まれています。そのために、2 つの も含まれています。1 つはデータ用で、もう 1 つは参照カウンター用です。これらの相互作用の良い例は、デストラクタで最もよく見られます。CCriticalSectionCGuardCImageCSharedMemoryCCriticalSection

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コードの詳細はこちら

4

3 に答える 3

5

「無効なハンドルが指定されました」という戻りコードは、クリティカルセクションオブジェクトの割り当てが解除されたことを非常に明確に示しています。もちろん、そもそも適切に割り当てられていると仮定します。

あなたのRAIIクラスはおそらく犯人のようです。一歩下がって考えてみると、RAIIクラスには2つの仕事があるため、懸念の分離の原則に違反しています。

  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、クリティカルセクションを実際に「所有」している人は誰もいません。また、別のエリアで少し稼ぐために、エリアのコントロールを少しあきらめました。

この問題の真の「修正」は、懸念を分離することです。割り当てと割り当て解除のために1つのクラスがあります。

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

...そしてもう1つはロックとロック解除を処理します...

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

これで、生のポインターまたは単一のCCriticalSectionオブジェクト(場合によってはconst)への参照を渡し、ワーカースレッドに独自のCSLockをインスタンス化させることができます。CSLockは、それを作成したスレッドによって所有されます。これは本来あるべきことですが、CCriticalSectionの所有権は、いくつかの制御スレッドによって明確に保持されます。また良いことです。

于 2011-08-19T15:47:59.333 に答える
1
  • クリティカル セクション オブジェクトが#pragmaパッキング 1 (または既定以外のパッキング) に含まれていないことを確認します。
  • 他のスレッド (または同じスレッド) が CS オブジェクトを破損していないことを確認してください。静的解析ツールを実行して、バッファ オーバーランの問題がないかどうかを確認します。
  • ランタイム分析ツールがある場合は、それを実行して問題を見つけてください。
于 2011-08-19T16:01:45.587 に答える
1

私はKISSの原則に従うことに決め、ロックンロールオールナイト物事を単純化します。CSharedMemoryClassをastd::tr1::shared_ptr<BYTE>と aに置き換えてCCriticalSection、同時アクセスから保護することにしました。どちらも現在のメンバーでCImageあり、懸念事項は今ではより適切に分離されています、私見。

これで奇妙なクリティカル セクションは解決されましたが、メモリ リークが原因で発生したようstd::tr1::shared_ptrです。この問題については、近日中に投稿する予定です。終わりはありません。

于 2011-08-25T19:54:59.930 に答える