次の方法で共有


競合状態とデッドロック

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 のみを完了します。 これは、 Thread 1 変数を読み取り、加算を完了したことを意味します。 スレッド 1 は、新しい値 150 の書き出しを待機しています。 Thread 1が停止すると、Thread 2が完全に実行されます。 これは、計算された値 (85) を変数 Totalに書き込んだことを意味します。 最後に、 Thread 1 は制御を回復し、実行を終了します。 その値 (150) を書き出します。 したがって、 Thread 1 が完了すると、 Total の値は 85 ではなく 150 になります。

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

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

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

競合状態の症状

競合状態の最も一般的な現象は、複数のスレッド間で共有される変数の予測不可能な値です。 これは、スレッドが実行される順序の予測不能性に起因します。 1 つのスレッドが優先される場合と、他のスレッドが優先される場合があります。 それ以外の場合は、実行が正しく機能します。 また、各スレッドを個別に実行すると、変数の値が正しく動作します。

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

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

デッドロックの詳細と例

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

  • スレッド 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
    

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

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

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

デッドロックの症状

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

スレッドとは

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

関連情報

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

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

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

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