帰ってきたヤマタノオロチw
本体一つに8つの首があるやまたのおろちさんが、一つの酒ツボから酒をグビグビとマルチスレッドに飲んでるプログラミングのお話ね。
まじめにマルチスレッドについてMSDN読んでたら、ふと、こんなページを発見。
ReaderWriterLockSlim クラス (System.Threading)
これは便利そう。マルチスレッドでプログラミングして何が大変って、変数の同期と排他だと思ってるから、それをまとめて簡単に処理させてくれるならとっても素敵。
というわけで、リベンジしたリファクタソース
Imports System
Imports System.Threading
Module OrochMTA
Delegate Sub DrinkingSAKE()
'酒ツボに酒を準備
Public inSakeTubo As New SakeTubo(40000)
_
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
Public Class OrochiHead
'おろちの首クラス
'お前は何番目なのかを保持
Private meNumber As Integer
'何番目の首?初期化w
Public Sub New(ByVal headNo As Integer)
meNumber = headNo
End Sub
'酒を飲むメソッド
Public Sub Drink()
Dim nowDrink As Integer
Dim nowNokori As Integer
Dim oneDrink As New Random
Dim oneBreath As New Random
'酒ツボの中の酒を飲み干す
While Not (inSakeTubo.Nomihoshited)
'一回飲むたびに息継ぎ
Thread.Sleep(oneBreath.Next(30, 60))
'酒つぼから飲んでみる
If inSakeTubo.Nomihoshited Then
Console.WriteLine("{0}番目の首「もう酒がねぇよ!!ぷんすか」", meNumber)
Exit Sub
Else
'一回ぐび
nowDrink = oneDrink.Next(1, 100)
nowNokori = inSakeTubo.Nokori
inSakeTubo.Gubi(nowDrink)
If inSakeTubo.Nomihoshited Then
Console.WriteLine("{0}番目の首「ちきしょう!足りねぇよ!{1}のみてぇのに{2}しかねぇ!」", meNumber, nowDrink, nowNokori)
Exit Sub
Else
Console.WriteLine("{0}番目の首が {1} リットル ぐびっ : 残り {2} リットル", meNumber, nowDrink, inSakeTubo.Nokori)
End If
End If
End While
Console.WriteLine("ぷはーっ! {0}番目の首、飲み終わり!", meNumber)
End Sub
End Class
Public Class SakeTubo
'酒つぼクラス
'マルチスレッドでの読み書き保護処理を集約
Private cacheLock As New ReaderWriterLockSlim()
Private innerSake As Integer
Public Sub New(ByVal litter As Integer)
innerSake = litter
End Sub
Public Function Nokori() As Integer
cacheLock.EnterReadLock()
Try
Return innerSake
Finally
cacheLock.ExitReadLock()
End Try
End Function
Public Sub Gubi(ByVal gubigubi As Integer)
Dim retFlag As Boolean = True
cacheLock.EnterWriteLock()
Try
If innerSake > gubigubi Then
innerSake -= gubigubi
Else
innerSake = 0
End If
Finally
cacheLock.ExitWriteLock()
End Try
End Sub
Public Sub Karappo()
cacheLock.EnterWriteLock()
Try
innerSake = 0
Finally
cacheLock.ExitWriteLock()
End Try
End Sub
Public Function Nomihoshited() As Boolean
cacheLock.EnterReadLock()
Try
Return (innerSake.Equals(0))
Finally
cacheLock.ExitReadLock()
End Try
End Function
End Class
End Module
長いorz
結果はこんな感じ。
あ、ちなみに、実行したマシンのCPUは せんとりーの♪せんとりーの♪せんとりーーーーのー♪マルチスレッドしてるーのー♪
シングルCPUではあるけれど、酒つぼクラスにちゃんと排他ロジックいれてないとうまく動かない。
あくまでも実験則なんだけれど、CPUの種類と数に依存するわけでなく、マルチスレッドという手法でプログラムを走らせてしまう時点で、それぞれのスレッドが参照する特定アドレスの中身が一律に保証されるためには排他が必須ってことだけは理解。こまけぇことはいいんだよ<違っ
図にするとこん感じ。酒つぼは1クラスにして、その中で読み書きの排他を実現しておいて、おろちの首クラスからは何も気にしないで変数を読み書きしてもらう仕組み。
並行してできることは、どんどん並行してもらっていいんじゃないかなと思っているので、大量データの処理なんかには有効なんじゃないかしら?実現したいことが「並行でできるのかどうか」を見極めるのは難しい部分はあるかもだけどね。
このソースを基準に、今度はスサノオさんに登場してもらおうと思ってます。例外処理ってやつねv