7

私に受け継がれた古いコードを実行しているときに問題が発生しました。99% の確率で動作しますが、「違反の読み取り場所」という例外が発生することがあります。プロセスの存続期間全体でこのコードを実行する可能性のあるスレッドの数は可変です。発生頻度が低いのは競合状態を示している可能性がありますが、この場合に例外が発生する理由はわかりません。問題のコードは次のとおりです。

MyClass::Dostuff()
{
    static map<char, int> mappedChars;
    if (mappedChars.empty())
    {
       for (char c = '0'; c <= '9'; ++c)
       {
          mappedChars[c] = c - '0';
       }
    }
    // More code here, but mappedChars in not changed.
}

operator[] への最初の呼び出しで、マップの operator[] 実装で例外がスローされます (STL の VS2005 実装を使用します)。


mapped_type& operator[](const key_type& _Keyval)
{
    iterator _Where = this->lower_bound(_Keyval); //exception thrown on the first line
    // More code here
}

私はすでに operator[] でスレッドをフリーズし、それらをすべて同時に実行しようとしましたが、その方法論を使用して例外を再現することはできませんでした。

それがスローされる理由を思いつくことができますか?

(はい、STL がスレッド セーフではないことはわかっています。ここで変更を加える必要があります。上記の動作が見られる理由について最も興味があります。)

リクエストに応じて、ここで例外に関する詳細をいくつか説明します:
app15-51-02-0944_2008-10-23.mdmp の 0x00639a1c (app.exe) で未処理の例外: 0xC0000005: アクセス違反の読み取り場所 0x00000004。

マルチスレッドの問題の解決策を提案してくれた皆さんに感謝しますが、これはこの質問が意図しているものではありません。はい、提示されたコードが正しく保護されておらず、達成しようとしていることが過剰であることは理解しています。私はすでにそれに対する修正を実装しています。そもそもこの例外がスローされた理由をよりよく理解しようとしています。

4

5 に答える 5

5

アドレスが「4」の場合、「this」ポインターが null であるか、反復子が正しくない可能性があります。デバッガーでこれを確認できるはずです。これが null の場合、問題はその関数にあるのではなく、その関数を呼び出している人に問題があります。イテレータが悪い場合、それはあなたがほのめかした競合状態です。ほとんどの反復子は、リストの更新を許容できません。

オーケー待って - ここには FM はありません。静的は最初の使用時に初期化されます。これを行うコードは、マルチスレッド セーフではありません。1 つのスレッドは初期化を行っていますが、2 つ目のスレッドは既に完了していると認識していますが、まだ進行中です。その結果、初期化されていない変数が使用されます。これは、以下のアセンブリで確認できます。

static x y;
004113ED  mov         eax,dword ptr [$S1 (418164h)] 
004113F2  and         eax,1 
004113F5  jne         wmain+6Ch (41141Ch) 
004113F7  mov         eax,dword ptr [$S1 (418164h)] 
004113FC  or          eax,1 
004113FF  mov         dword ptr [$S1 (418164h)],eax 
00411404  mov         dword ptr [ebp-4],0 
0041140B  mov         ecx,offset y (418160h) 
00411410  call        x::x (4111A4h) 
00411415  mov         dword ptr [ebp-4],0FFFFFFFFh

$S1 は、初期化時に 1 に設定されます。設定されている場合 (004113F5) は、init コードを飛び越えます。このチェックは関数への入り口で行われるため、fnc でスレッドをフリーズしても役に立ちません。これは null ではありませんが、メンバーの 1 つが null です。

マップをメソッドから静的クラスに移動して修正します。その後、起動時に初期化されます。それ以外の場合は、do DoStuff() の呼び出しを CR で囲む必要があります。マップ自体の使用に関する CR を配置することで、残りの MT の問題から保護できます (たとえば、DoStuff が operator[] を使用する場所)。

于 2008-10-23T23:19:37.217 に答える
3

MappedChars は静的であるため、DoStuff() を実行するすべてのスレッドで共有されます。それだけであなたの問題になる可能性があります。

静的マップを使用する必要がある場合は、ミューテックスまたはクリティカル セクションで保護する必要がある場合があります。

個人的には、この目的で地図を使用するのはやり過ぎだと思います。char を受け取り、そこから '0' を減算するヘルパー関数を作成します。関数でスレッド セーフの問題が発生することはありません。

于 2008-10-23T23:09:35.753 に答える
2

複数のスレッドが関数を呼び出している場合、DoStuffこれは初期化コードが

if (mappedChars.empty())

競合状態に入ることができます。これは、スレッド 1 が関数に入り、マップが空であることを検出し、マップへの入力を開始することを意味します。次に、スレッド 2 が関数に入り、マップが空ではない (ただし完全に初期化されていない) ことを検出したので、喜んで読み取りを開始します。両方のスレッドが競合しているが、一方がマップ構造を変更している (つまり、ノードを挿入している) ため、未定義の動作 (クラッシュ) が発生します。

マップをチェックする前に同期プリミティブを使用し、empty()マップが完全に初期化されたことが保証された後に解放すると、すべてがうまくいきます。

Googleで調べたところ、実際に静的初期化はスレッド セーフではありません。したがって、宣言static mappedCharsはすぐに問題になります。他の人が述べたように、初期化の存続期間中、1 つのスレッドのみがアクティブであることが保証されているときに初期化が行われた場合に最適です。

于 2008-10-23T23:09:03.727 に答える
1

マルチスレッディングを開始すると、状況が常に変化するため、問題が発生している正確な場所を正確に特定するにはあまりにも多くのことが行われます。マルチスレッドの状況で静的マップを使用すると問題が発生する可能性がある場所がたくさんあります。

静的変数を保護するいくつかの方法については、このスレッドを参照してください。おそらく最善の策は、複数のスレッドを起動して初期化する前に、関数を 1 回呼び出すことです。それか、静的マップを移動して、別の初期化メソッドを作成します。

于 2008-10-23T23:26:48.793 に答える
0

operator[]範囲外の引数で呼び出したことはあります0..9か?その場合、誤ってマップを変更しているため、他のスレッドで問題が発生している可能性があります。マップにまだ存在しない引数を指定して呼び出すoperator[]と、値タイプのデフォルト値(の場合は0)に等しい値でそのキーがマップに挿入されますint

于 2008-10-24T04:40:11.517 に答える