連載:VB 6ユーザーのための
これならマスターできるVB 2005超入門

第11回 初めてのマルチスレッドと排他制御入門

羽山 博
2007/09/11
Page1 Page2 Page3 Page4

サンプル・プログラム17 − Mutexによる排他制御

 サンプル・プログラム16では預金を引き落とす(引き出す)スレッドを1つ作成して実行したが、口座管理に限らず、同じファイルやデータに複数の処理が同時に行われることはよくある。次に、そのような処理の例を見てみよう。

 ここではスレッドをもう1つ増やし、同時に処理を実行するようにプログラムを発展させる。また、そのときに起こり得る問題と、その問題を回避するための排他制御についても、追って見ていくこととしよう。

■複数のスレッドを作成・実行する

 当然のことながら、フォームのデザインにはまったく変更はない。ただ、コードを記述してスレッドをもう1つ作成し、実行すればいい。ここでは、新しいスレッドは預け入れをするような処理にしてみよう。つまり、decDepositに正の値を代入して、スレッドを作成、実行しようというわけだ。

 方法はさほど難しくはない。スレッドを作成、実行するためのコードをもう1つ書けばよい。実はこれまた正しい結果が得られないのだが、取りあえずは動くコードとなっている。ステップを追っていくので、焦らずに一歩ずつ進むこととしよう。

Imports System.Threading

Public Class frmMTsample

  Private decBalance As Decimal = 50000D ' 最初の残高
  Private intInterval As Integer ' 処理にかかる時間
  Private Sub mainProc(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.Click

    Dim decDeposit As Decimal ' 預入額または引出額

    Dim myThread1 As Thread
    myThread1 = New Thread(AddressOf WithDraw)

    Dim myThread2 As Thread
    myThread2 = New Thread(AddressOf WithDraw)
    '
    intInterval = 5000 ' 5秒かかるものとする
    decDeposit = -20000D ' 20000円引き落とすものとする

    myThread1.Name = "引き落とし"
    myThread1.Start(decDeposit) ' スレッド1を開始する

    Thread.Sleep(1000) ' 少し待つ

    intInterval = 1000 ' 1秒かかるものとする
    decDeposit = 30000D ' 30000円預け入れるものとする

    myThread2.Name = "預け入れ"
    myThread2.Start(decDeposit) ' スレッド2を開始する

    myThread1.Join() ' スレッドの終了を待つ
    myThread2.Join() ' スレッドの終了を待つ

    ' 新しい残高を表示する
    lblBalance.Text = decBalance.ToString

  End Sub

  Private Sub WithDraw(ByVal data As Object)

    Dim decWork As Decimal

    Debug.Print(Thread.CurrentThread.Name & "開始")
    decWork = decBalance ' 残高の読み出し
    decWork = decWork + Ctype(data, Decimal) ' 預金または引き出し

    Thread.Sleep(intInterval) ' 処理にかかる時間

    decBalance = decWork ' 残高の書き出し
    Debug.Print(Thread.CurrentThread.Name & "終了")
  End Sub
End Class
複数のスレッドを作成し、実行するコード
  もう1つスレッドを作成する。
  ThreadクラスのNameプロパティにスレッド名を代入する(myThread1の方は「引き落とし」とする)。
  ThreadクラスのNameプロパティにスレッド名を代入する(myThread2の方は「預け入れ」とする)。
  ThreadクラスのCurrentThreadプロパティで現在のスレッドを取得し、その名前と「開始」をイミディエイト・ウィンドウに表示する。
  ThreadクラスのCurrentThreadプロパティで現在のスレッドを取得し、その名前と「終了」をイミディエイト・ウィンドウに表示する。

 スレッドの作成方法は、最初のプログラムとまったく同じ。 がそのコード。スレッドを作成して、myThread2で参照できるようにしている。 にある、ThreadクラスのNameプロパティにはスレッドの名前を設定できる。このプロパティには値が1回だけしか設定できないので要注意。これらのコードは、現在どのスレッドが実行されているのかを確認するために付け加えたものだ。

 WithDrawプロシージャに目を移してみると、ThreadクラスのCurrentThreadプロパティを利用して、現在のスレッドを取得し、Nameプロパティの値をイミディエイト・ウィンドウに表示していることが分かる。従って、イミディエイト・ウィンドウの表示を見れば、どのスレッドが処理を開始したか、終了したかという状況が分かる。

 では、実行結果を見てみよう(図4)。イミディエイト・ウィンドウを見れば、確かに複数のスレッドが実行されていることが分かる。が、フォームに結果として表示された残高が少しおかしい。当初の残高が50000円で、そこから20000円引き落とした後、30000円預けるわけなので、残高は、

50000 − 20000 + 30000 = 60000

となるはずなのだが、結果は30000円になってしまう。


図4 複数のスレッドが同じデータを同時に扱うと結果がおかしくなることがある
本来は60000となるはずの結果が30000にしかならない。イミディエイト・ウィンドウに表示されたスレッドの処理状況を見ると、引き落としの開始と終了の間に、預け入れの処理が終わってしまっていることが分かる。

 これでは、せっかく預金した30000円がなかったことになってしまう。スレッドは同時に並行して実行されるので、処理のタイミングによっては、このような問題が起こることがある。

 このプログラムの場合、引き落としのスレッドが残高を更新する前に、預け入れのスレッドが残高を読み出しているのが原因だ。図で表してみると、その様子がよく分かる(図5)。


図5 スレッドの処理状況
処理を丁寧に追いかけてみれば、正しい結果が得られない理由が分かる。
  引き落としスレッドが残高を読み出す。
  預け入れスレッドが残高を読み出す。
  残高に30000円加算する。
  残高を書き戻す。残高は80000円になる。
  引き落としスレッドが残高(預け入れの前に読み出したので50000円)から20000円引く。
  残高を書き戻す。残高は30000円になる。

 マルチスレッドのプログラムに限らず、複数のコードから同じデータを同時に取り扱うと、このような問題が起こる危険がある。

 問題を回避するための最も簡単な方法としては、スレッドのJoinメソッドを使うという方法も考えられる。しかし、それでは汎用性に欠ける。例えば、Joinメソッドを使って引き落としスレッドの実行が終わるのを待ってから、預け入れスレッドを開始するということもできるが、実際には必ずしも引き落としが先に行われて、預け入れが後で行われるとは限らない。どちらが先に実行されても、データの更新が終わるのを待つようにしなければ意味がない。

 より汎用的で確実な方法としては、排他制御が使われる。排他制御とは、特定のデータやコードを利用しているときには、ほかからのアクセスを禁止するという制御のこと。この例であれば、先に残高を読み出したスレッドが残高を書き戻すまで、ほかのスレッドから残高にアクセスすること(実際には残高を読み出して実行するコードの実行)を禁止すればよい。

 排他制御を実現する方法にはいくつかの方法があるが、「Mutex(ミューテックス)」を利用するのが簡単だろう。というわけで、次にMutexを利用した排他制御を見ていこう。


 INDEX
  連載:VB 6ユーザーのための<BR>これならマスターできるVB 2005超入門
  第11回 初めてのマルチスレッドと排他制御入門
    1.サンプル・プログラム16 − 初めてのマルチスレッド・プログラム(1)
    2.サンプル・プログラム16 − 初めてのマルチスレッド・プログラム(2)
  3.サンプル・プログラム17 − Mutexによる排他制御
    4.Mutexを利用した排他制御を組み込む
 
インデックス・ページヘ  「これならマスターできるVB 2005超入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

注目のテーマ

業務アプリInsider 記事ランキング

本日 月間
ソリューションFLASH