競合状態とデッドロック

Visual Basic .NET または Visual Basic では、Visual Basic アプリケーションでスレッドを初めて使用できます。 スレッドでは、競合状態やデッドロックなどのデバッグの問題が発生します。 この記事では、これら 2 つの問題について説明します。

元の製品バージョン: Visual Basic、Visual Basic .NET
元の KB 番号: 317723

競合状態が発生した場合

競合状態は、2 つのスレッドが共有変数に同時にアクセスするときに発生します。 最初のスレッドは変数を読み取り、2 番目のスレッドは変数から同じ値を読み取ります。 次に、最初のスレッドと 2 番目のスレッドが値に対して操作を実行し、共有変数に最後に値を書き込めるスレッドを確認します。 値を最後に書き込むスレッドの値は保持されます。これは、スレッドが前のスレッドが書き込んだ値に対して書き込まれているためです。

競合状態の詳細と例

各スレッドには、プロセッサで実行する定義済みの期間が割り当てられます。 スレッドに割り当てられた時間が経過すると、スレッドのコンテキストは次にプロセッサをオンにし、プロセッサが次のスレッドの実行を開始するまで保存されます。

1 行のコマンドが競合状態を引き起こす方法

次の例を調べて、競合状態がどのように発生するかを確認します。 スレッドは 2 つあり、どちらも total (アセンブリ コードのように表される dword ptr ds:[031B49DCh] ) と呼ばれる共有変数を更新しています。

  • [スレッド 1]

    Total = Total + val1
    
  • [スレッド 2]

    Total = Total - val2
    

前の Visual Basic コードのコンパイルからのアセンブリ コード (行番号付き)。

  • [スレッド 1]

    1. mov eax,dword ptr ds:[031B49DCh]
    2. add eax,edi
    3. jno 00000033
    4. xor ecx,ecx
    5. call 7611097F
    6. mov dword ptr ds:[031B49DCh],eax
    
  • [スレッド 2]

    1. mov eax,dword ptr ds:[031B49DCh]
    2. sub eax,edi
    3. jno 00000033
    4. xor ecx,ecx
    5. call 76110BE7
    6. mov dword ptr ds:[031B49DCh],eax
    

アセンブリ コードを見ると、単純な加算計算を実行するために、プロセッサが下位レベルで実行している操作の数を確認できます。 スレッドは、プロセッサ上でアセンブリ コードの全部または一部を実行できる場合があります。 次に、このコードから競合状態がどのように発生するかを確認します。

Total は 100、 val1 は 50、 val2 15 です。 スレッド 1 は実行する機会を得ますが、手順 1 から 3 のみを完了します。 これは、 スレッド 1 が変数を読み取り、追加を完了したことを意味します。 スレッド 1 は、新しい値 150 の書き出しを待機しています。 スレッド 1 が停止すると、スレッド 2 は完全に実行されます。 これは、計算した値 (85) が変数 Totalに書き込まれたことを意味します。 最後に、 スレッド 1 が制御を取り戻し、実行を完了します。 値 (150) を書き出します。 そのため、 スレッド 1 が完了すると、 の Total 値は 85 ではなく 150 になります。

これが大きな問題である可能性がある方法を確認できます。 これが銀行プログラムの場合、顧客は口座に存在してはならないお金を持っています。

このエラーはランダムです。 これは、スレッド 1 がプロセッサの有効期限が切れる前に実行を完了し、 スレッド 2 がその実行を開始できるためです。 これらのイベントが発生した場合、問題は発生しません。 スレッドの実行は決定的ではありません。そのため、実行の時間や順序を制御することはできません。 また、スレッドは実行時とデバッグ モードで異なる方法で実行される可能性があることにも注意してください。 また、各スレッドを連続して実行すると、エラーは発生しないことがわかります。 このランダム性により、これらのエラーの追跡とデバッグがはるかに困難になります。

競合状態が発生しないように、共有変数をロックして、一度に 1 つのスレッドのみが共有変数にアクセスできるようにすることができます。 スレッド 1 で変数がロックされ、スレッド 2 にも変数が必要な場合、スレッド 2 はスレッド 1 が変数を解放するのを待機している間にスレッド 2 の実行が停止するため、控えめに行います。 (詳細については、この記事の「参照」セクションを参照してくださいSyncLock)。

競合状態の症状

競合状態の最も一般的な症状は、複数のスレッド間で共有される変数の予測できない値です。 これは、スレッドが実行される順序の予測不能性から生じます。 1 つのスレッドが優先される場合もあれば、もう一方のスレッドが優先される場合もあります。 それ以外の場合、実行は正しく動作します。 また、各スレッドが個別に実行される場合、変数値は正しく動作します。

デッドロックが発生した場合

デッドロックは、2 つのスレッドがそれぞれ異なる変数を同時にロックし、もう一方のスレッドが既にロックしている変数をロックしようとすると発生します。 その結果、各スレッドは実行を停止し、他のスレッドが変数を解放するのを待機します。 各スレッドは、他のスレッドが必要とする変数を保持しているため、何も発生しないため、スレッドはデッドロック状態のままです。

デッドロックの詳細と例

次のコードには、2 つのオブジェクトと RightValがありますLeftVal

  • [スレッド 1]

    SyncLock LeftVal
        SyncLock RightVal
            'Perform operations on LeftVal and RightVal that require read and write.
        End SyncLock
    End SyncLock
    
  • [スレッド 2]

    SyncLock RightVal
        SyncLock LeftVal
            'Perform operations on RightVal and LeftVal that require read and write.
        End SyncLock
    End SyncLock
    

スレッド 1 で をロックLeftValできる場合、デッドロックが発生します。 プロセッサは スレッド 1 の実行を停止し、 スレッド 2 の実行を開始します。 スレッド 2 がロック RightVal され、ロックが LeftVal試行されます。 がロックされているため LeftValスレッド 2 は停止し、解放されるまで待機 LeftVal します。 スレッド 2 が停止しているため、スレッド 1 の実行を続行できます。 スレッド 1 はロック RightVal を試行しますが、 スレッド 2 によってロックされているため、ロックできません。 その結果、 スレッド 1 は RightVal が使用可能になるまで待機を開始します。 各スレッドは、他のスレッドが待機している変数をロックしており、どちらのスレッドも保持している変数のロックを解除していないため、各スレッドは他のスレッドを待機します。

デッドロックは常に発生するとは限りません。 プロセッサが停止する前に スレッド 1 が両方のロックを実行する場合、 スレッド 1 はその操作を実行してから、共有変数のロックを解除できます。 スレッド 1 が変数のロックを解除すると、スレッド 2 は期待どおりに実行を続行できます。

このエラーは、コードのこれらのスニペットが並べて配置されている場合は明らかですが、実際には、コードがコードの別のモジュールまたは領域に表示される場合があります。 これは、この同じコードから正しい実行と正しくない実行の両方が発生する可能性があるため、追跡するのが難しいエラーです。

デッドロックの症状

デッドロックの一般的な症状は、プログラムまたはスレッドのグループが応答を停止することです。 これはハングとも呼ばれます。 少なくとも 2 つのスレッドが、もう一方のスレッドがロックされている変数を待機しています。 どちらのスレッドも、他の変数を取得するまで変数を解放しないため、スレッドは続行されません。 プログラムがそれらのスレッドの一方または両方で実行を完了するのを待機している場合、プログラム全体がハングする可能性があります。

スレッドとは

プロセスは、1 台のコンピューターで指定した時刻に実行されているさまざまなアプリケーションを分離するために使用されます。 オペレーティング システムはプロセスを実行しませんが、スレッドは実行します。 スレッドは実行単位です。 オペレーティング システムは、スレッドのタスクの実行にプロセッサ時間をスレッドに割り当てます。 1 つのプロセスに複数の実行スレッドを含めることができます。 各スレッドは、独自の例外ハンドラー、スケジュールの優先順位、およびスレッドがプロセッサに割り当てられている間にスレッドの実行を完了できない場合にスレッドのコンテキストを保存するために使用する構造体のセットを維持します。 コンテキストは、スレッドが次にプロセッサ時間を受け取るまで保持されます。 コンテキストには、スレッドの実行をシームレスに継続するために必要なすべての情報が含まれています。 この情報には、スレッドのプロセッサ レジスタのセットと、ホスト プロセスのアドレス空間内の呼び出し履歴が含まれます。

関連情報

詳細については、Visual Studio ヘルプで次のキーワードを検索してください。

  • SyncLock. オブジェクトのロックを許可します。 別のスレッドがその同じオブジェクトをロックしようとすると、最初のスレッドが解放されるまでブロックされます。 問題は SyncLock の誤用に起因する可能性があるため、慎重に使用 SyncLock してください。 たとえば、このコマンドは競合状態を防ぐことができますが、デッドロックが発生します。

  • InterLocked. 基本的な数値変数に対するスレッド セーフ操作の選択セットを許可します。

詳細については、「 スレッドとスレッド」を参照してください。