やまたのおろちはお酒大好き。とりあえず前回は代表の首一本だけが酒ツボからグビグビいっちゃう想定でシングルスレッドのお話をしてたりなんかしたのね。
http://blogs.wankuma.com/esten/archive/2008/12/03/162607.aspx
でも、これだと他の酒のみ待ち首はきっとウルサイ。先に飲ませろ、俺に飲ませろ、まぁ黙れやこの飲兵衛よっぱらい爬虫類もどきドモがっ!といいたくなるような状況にきっとなっちゃう。
そこで、全部の首に、いっせーーーーの!で飲ませようというのが今回のお話、マルチスレッドね。
実は前回で、一つの首が酒を飲むのにデリゲートしてシングルスレッドで飲ませるところまではできた。つまり簡単に考えれば、シングルになっているところをマルチにするだけで、事は解決できるはず。
Imports System.Threading
Module OrochMTA
Delegate Sub DrinkingSAKE()
<MTAThread()> _
Public Sub Main()
'本体で首が別々に酒飲みするのを身構える
Dim ts As New ThreadStart(AddressOf OrochiDrinking)
Dim workerThread As New Thread(ts)
workerThread.SetApartmentState(ApartmentState.MTA)
workerThread.Start()
workerThread.Join()
Stop
End Sub
Private Sub OrochiDrinking()
'まずは首を八本で飲んでみる
Dim headCount As Integer = 8
'酔っ払い待ち行列を首の数だけ準備
Dim yoppa(headCount - 1) As WaitHandle
For i As Integer = 1 To headCount
'飲んだ首から酔っ払いへと
yoppa(i - 1) = HeadDrinking(i).AsyncWaitHandle
Next
'全部の首が酔っ払いになるまで待機
WaitHandle.WaitAll(yoppa)
End Sub
Private Function HeadDrinking(ByVal headNo As Integer) As IAsyncResult
'指定した番号の首に酒を飲ませる
Dim myHead As New OrochiHead(headNo)
Dim gubi As New DrinkingSAKE(AddressOf myHead.Drink)
'酒飲み開始とともに、酔っ払い待ち行列へ戻す
Return gubi.BeginInvoke(Nothing, Nothing)
End Function
'おろちの首をクラス化
Private Class OrochiHead
Private meNumber As Integer
'何番目の首なのかを保存
Public Sub New(ByVal headNo As Integer)
meNumber = headNo
End Sub
'酒を飲むメソッド
Public Sub Drink()
Dim sakeZuki As New Thread( _
New ThreadStart(AddressOf DrinkSAKE)) '
sakeZuki.Start()
sakeZuki.Join()
Console.WriteLine("ぷはーっ! {0}番目の首、飲み終わり!", meNumber)
End Sub
' 酒のみスレッド
Private Sub DrinkSAKE()
'酒ツボの中の酒の量
Dim totalSAKE As Integer = 1000
Dim totalDrunk As Integer = 0
Dim nowDrink As Integer
Dim oneDrink As New Random
Dim oneBreath As New Random
'酒ツボの中の酒を飲み干す
While (totalDrunk <= totalSAKE)
'一回飲むたびに息継ぎ
Thread.Sleep(oneBreath.Next(30, 60))
'一回ぐび
nowDrink = oneDrink.Next(1, 100)
Console.WriteLine("{0}番目の首が {1} リットル ぐびっ", meNumber, nowDrink)
'全部でどれくらい飲んだ?
totalDrunk += nowDrink
End While
End Sub
End Class
End Module
変わった部分、あんまり無い(笑)。でも動かしてみると判るけど、全部の首が並列でぐびぐび酒飲んじゃう。
ポイントは、
7行目:<MTAThread()>
16行目:workerThread.SetApartmentState(ApartmentState.MTA)
これによって、「これから動かすスレッドは並列で動かすつもりなんでよろしく!」と準備して、
54行目:Return gubi.BeginInvoke(Nothing, Nothing)
で実際に、スレッドが「非同期で」起動、でもって、一つ一つのスレッドの背番号(IAsyncResultクラスのWaitHandle)をうけとって
42行目:WaitHandle.WaitAll(yoppa)
で全部の背番号にぶら下がったスレッドが終了するまで処理を待機する、ってところ。
なんだ、簡単じゃぁん!なんて思うでしょ?実際、簡単に見えてくるもの。でもこれがでっかいでっかい落とし穴。動くけど、このソースだけじゃまだまだダメだったりなんかする。過信するなよ、.NetFrameworkプログラミング(笑)
実は村で用意したのは馬鹿でかい酒ツボ一個。だから、首一つにつき酒ツボ一つじゃなくて、全部の首で一つの酒ツボから酒を飲むことになっちゃった!
って、この酒ツボが一個になった時に起きる時、マルチスレッドプログラミングで落っこちる地獄への入り口で、これがまた、ふっかーーい話だったりするの。覚悟せよ、次回(笑)