5

クリティカル セクションの使用に問題があります。私のアプリには多数のスレッド (たとえば 60) があり、それらすべてがグローバル リソースにアクセスする必要があります。したがって、そのリソースをクリティカル セクションで保護します。これは操作中は完全に機能しますが、アプリケーションがシャットダウンすると、スレッドが終了し、クリティカル セクションが破棄されます。

問題は、これらのスレッドの一部が終了時にクリティカル セクションで待機している場合に発生し、その結果、スレッド自体の終了がブロックされます。

「Initialized」フラグを持つ Windows CriticalSection 呼び出しのラッパーを作成しました。このフラグは、クリティカルが作成されたときに true に設定され、クリティカルを離れようとしているときに false に設定されます (どちらの場合も、内部にあるときに設定されます)。クリティカル)。このフラグは、'enter crit' ラッパー関数がクリティカルに入ろうとする前にチェックされ、フラグが false の場合はリクエストをバイパスします。フラグは、スレッドが正常にクリティカルに入った瞬間にもチェックされ、false の場合はすぐにクリティカルから離れます。

クリティカルを削除する前に私がすべきことは、フラグを false に設定してから、待機中のスレッドが次のようになるまで待機することです。Initialized フラグが false であることを確認してください。次に、クリティカルを残します(これは、各スレッドの操作が非常に迅速になるはずです)。

CRITICAL_SECTION 構造体内の LockCount をチェックすることで、クリティカルへのアクセスを待機しているスレッドの数をチェックし、それが 0 になるまで待ちます (XP ではLockCount - (RecursionCount-1); 2003 サーバー以降では、ロック カウントは です((-1) - (LockCount)) >> 2)。クリティカルセクション。

これで十分なはずですが、クリティカルに入るのを待っているスレッドが 1 つ (常に 1 つのスレッドのみで、それ以上になることはありません) ある場合、LockCount が 0 に達することがわかりました。つまり、その時点でクリティカルを削除すると、他のスレッドが引き続きウェイクします。 CRITICAL_SECTION オブジェクトがその時点までに破棄されているため、クラッシュが発生します。

アクセスを待機しているスレッドの内部ロック数を保持している場合、正しい数が得られます。ただし、このカウントをクリティカルの外でインクリメントする必要があるため、これは理想的ではありません。つまり、値は保護されていないため、一度に完全に信頼することはできません。

CRITICAL_SECTION 構造体の LockCount が 1 になる理由を知っている人はいますか? 独自のロック カウントを使用する場合、最後のスレッドが終了した後(およびクリティカルを破棄する前) にCRITICAL_SECTION のロック カウントを確認すると、まだ 0 です...

または、クリティカル セクション以外に、アプリ内のグローバル リソースを多くのスレッドで保護するためのより良い方法はありますか?

これは私のラッパー構造です:

typedef struct MY_CRIT {
    BOOL Initialised;
    CRITICAL_SECTION Crit;
    int MyLockCount;
}

これが私の Crit init 関数です。

BOOL InitCrit( MY_CRIT *pCrit )
{
    if (pCrit)
    {
        InitializeCriticalSection( &pCrit->Crit );          
        pCrit->Initialised = TRUE;
        pCrit->MyLockCount = 0;
        return TRUE;
    }
    // else invalid pointer
    else    
        return FALSE;
}

これは私のEnter Critラッパー関数です:

BOOL EnterCrit( MY_CRIT *pCrit )
{
    // if pointer valid, and the crit is initialised
    if (pCrit && pCrit->Initialised)
    {
        pCrit->MyLockCount++;
        EnterCriticalSection( &pCrit->Crit );
        pCrit->MyLockCount--;

        // if still initialised
        if (pCrit->Initialised)
        {
            return TRUE;
        }
        // else someone's trying to close this crit - jump out now!
        else
        {
            LeaveCriticalSection( &pCrit->Crit );
            return FALSE;
        }
    }
    else // crit pointer is null
        return FALSE;
}

そして、これが私の FreeCrit ラッパー関数です。

void FreeCrit( MY_CRIT *pCrit )
{
    LONG    WaitingCount = 0;

    if (pCrit && (pCrit->Initialised))
    {
        // set Initialised to FALSE to stop any more threads trying to get in from now on:
        EnterCriticalSection( &pCrit->Crit );
        pCrit->Initialised = FALSE;
        LeaveCriticalSection( &pCrit->Crit );

        // loop until all waiting threads have gained access and finished:
        do {
            EnterCriticalSection( &pCrit->Crit );

            // check if any threads are still waiting to enter:
            // Windows XP and below:
            if (IsWindowsXPOrBelow())
            {
                if ((pCrit->Crit.LockCount > 0) && ((pCrit->Crit.RecursionCount - 1) >= 0))
                    WaitingCount = pCrit->Crit.LockCount - (pCrit->Crit.RecursionCount - 1);
                else
                    WaitingCount = 0;
            }
            // Windows 2003 Server and above:
            else
            {
                WaitingCount = ((-1) - (pCrit->Crit.LockCount)) >> 2;
            }

                        // hack: if our own lock count is higher, use that:
            WaitingCount = max( WaitingCount, pCrit->MyLockCount );

            // if some threads are still waiting, leave the crit and sleep a bit, to give them a chance to enter & exit:
            if (WaitingCount > 0)
            {
                LeaveCriticalSection( &pCrit->Crit );
                // don't hog the processor:
                Sleep( 1 );
            }
            // when no other threads are waiting to enter, we can safely delete the crit (and leave the loop):
            else
            {
                DeleteCriticalSection( &pCrit->Crit );
            }
        } while (WaitingCount > 0);
    }
}
4

1 に答える 1

5

CS を破棄する前に、CS が使用されていないことを確認する責任があります。現在、他のスレッドが入ろうとしているわけではありませんが、すぐに入ろうとする可能性があるとしましょう。CS を破棄すると、この並行スレッドは何をするのでしょうか? フル ペースで削除されたクリティカル セクションにヒットし、メモリ アクセス違反が発生しますか?

実際の解決策は現在のアプリの設計によって異なりますが、スレッドを破棄している場合は、それらのスレッドを停止するリクエストにフラグを立ててから、スレッドが破棄されるのを待つためにハンドルを待機することをお勧めします。スレッドが完了したことを確認したら、クリティカル セクションの削除を完了します。

のような CS メンバーの値に依存するのは安全ではないことに注意して.LockCountくださいIsWindowsXPOrBelowCRITICAL_SECTIONクリティカル セクション APIでは、構造を「ブラック ボックス」として使用し、内部を実装固有のままにすることをお勧めします。

于 2012-05-14T12:03:07.597 に答える