Sleeping barber problem

id:sumimさんが投稿された、
居眠り床屋問題 http://ja.doukaku.org/285/
丁度F#の並列計算を(ちまちまですが、)勉強していたところだったので、標準クラスのMailboxProcessorを使って解いてみました。ここでこれを使うべきじゃないかもしれませんが、まあ男とはそういう生き物です。


MailboxProcessorとは、Erlangのプロセス間通信(メッセージ・パッシング)みたいなもので、スレッド間でメッセージをやり取りするための仕組み(を提供してくれるクラス)です。ライブラリレベルでの提供なので、Erlangほどしっかりしたものではないようです。(Erlangは良く分かってません)
名前から分かるように、相手のメールボックスにPostしておいて、それを好きなタイミングでReceiveするというのが基本的な流れです。ベイジアンフィルタは実装されていません。


気が向けば(僕は相当気まぐれですが)MailboxProcessorの簡単な解説も書くかもしれません*1。しかし今日はとりあえず回答を丸投げしておきます。
無理矢理な部分が多くて読みづらいと思うので、処理の流れを簡単に書いておきます。

  • 客を一度に生成して、
  • メインのスレッドで一人ずつ訪問(問い合わせメールを送信)させます。
  • サロンでは客(メール)が来ると席を確認し、カット可能かどうか返信(true|false)します。受付のお姉さんの仕事です。ちなみに彼氏持ちなので連絡先を聞いても無駄です。
  • 店内に案内されました。なかなか洒落た店内です。BGMが少しうるさいのが気になります。受付のお姉さんが美容師に客の身柄を受け渡します。(なんと大事なお客様はメールに添付されるのです)
  • この時点で美容師が寝ていれば起きます。当たり障りの無いつまらないトークをしながら、美容師はハサミを動かします。
  • カットが終了すると、「この人終わったよ」と受付のお姉さんに知らせます(送り返します)。なんとまたしてもお客様はメールに貼り付けられます。
  • その通知を見て、お姉さんは待機中の次の客を美容師に渡します。客がいなければ美容師は居眠りを始めます。※日中に慢性的に強い眠気を感じる方は、睡眠時無呼吸症候群などの疑いがあります。
  • この処理を、営業終了メールを受け取るまで繰り返します


というストーリーをえがいたのが以下のプログラムです。
せっかくの関数型言語ということで、意地になって破壊的代入を排除しました。(一部残ってますが・・・)

open System
open System.Threading
    
type Visitor (id:int, requiredTime:int) =
    member self.ID = id
    member self.RequiredTime = requiredTime
    
    static member GenVisitors () =
        let rnd = new Random()
        [ for id in 1..16 -> new Visitor(id, rnd.Next(100, 400)) ]
    
type Message =
/// 営業開始
| Open
/// ご来店〜
| Request   of AsyncReplyChannel<bool> * Visitor
/// カット終了!
| Finished  of Visitor
/// 閉店でーす
| Close

type StylistAction =
| Cut   of Visitor
| Sleep
| End

type Stylist = MailboxProcessor<StylistAction>

type Salon () =
    /// 空きが無ければNone, 空いてれば新しいseats
    let SetSeats (seats : Visitor list) newVisitor =
        if seats.Length >= 3 then None
        else Some (newVisitor :: seats)
        
    let rec Last = function
    | []        -> Sleep
    | x :: []   -> Cut x
    | _ :: cdr  -> Last cdr
    
    // 相互に参照させるための92939(苦肉の策)
    [<DefaultValue>] val mutable salon : MailboxProcessor<Message>
    [<DefaultValue>] val mutable stylist : Stylist


    member self.CreateSalon (stylist : Stylist) =
        let Post (newSeats : Visitor list) = stylist.Post <| Last newSeats ; newSeats
        
        // 処理の心臓部分
        let Switch seats = function
        | Open  -> Post seats
        | Close -> [(*´ω`*)]
        | Finished visitor ->
            Post <| List.filter (fun v -> v <> visitor) seats
        | Request (mail, visitor) ->
            match (SetSeats seats visitor) with
            | None          -> mail.Reply false ; seats
            | Some newSeats -> mail.Reply true  ; newSeats

        let salon' = new MailboxProcessor<Message>(fun mailbox ->
            let rec DoBusiness (seats : Visitor list) isSleeping = async {
                // 起きなきゃいけない?
                if isSleeping && seats.Length = 1 then stylist.Post <| Last seats
                
                let! msg = mailbox.Receive ()
                if msg = Close then return ()
                return! DoBusiness (Switch seats msg) (seats = [])
            }
            DoBusiness [] true
        )
        self.salon <- salon' ; salon'
        
    
    member self.CreateStylist () =
        let Cut (v : Visitor) =
            printfn "カット開始! %d" v.ID
            Thread.Sleep( v.RequiredTime )
            self.salon.Post (Finished v)
            printfn "カット終了! %d" v.ID
        
        let stylist = new Stylist(fun jobbox ->
            let rec MakeHair isSleeping = async {
                let! job = jobbox.Receive ()
                match job with
                | End -> return ()
                | Sleep ->
                    printfn "Zzz...(-_-)"
                    return! MakeHair true
                | Cut visitor  ->
                    if isSleeping then printfn "Σ(゚_゚)"
                    Cut visitor
                    return! MakeHair false
            }
            MakeHair false
        )
        self.stylist <- stylist ; stylist


    member self.Start () =
        let visitors = Visitor.GenVisitors ()
        let span = let r = Random() in r.Next(0, 200)
        use stylist = self.CreateStylist ()
        use salon = self.CreateSalon stylist
        let count = ref 0
        
        stylist.Start () ; salon.Start ()
        salon.Post Open
        for v in visitors do
            if v.ID = 9 then Thread.Sleep 1200
                        else Thread.Sleep span
            
            printfn "来店 %d" v.ID
            let ok = salon.PostAndReply (fun (x:AsyncReplyChannel<bool>) -> Request (x,v))
            if ok   then incr count
                    else printfn "満席で立ち去る %d" v.ID
        done
        // 仕事終わりを待ってから終了メールを出す
        Thread.Sleep 1200
        stylist.Post End
        salon.Post Close
        printfn "※ %d人のうち%d人をカット" visitors.Length (!count)


let Test_Salon () =
    (Salon ()).Start () |> ignore

出力例:

Zzz...(-_-)
来店 1
Σ(゚_゚)
カット開始! 1
来店 2
来店 3
カット終了! 1
カット開始! 2
来店 4
来店 5
満席で立ち去る 5
カット終了! 2
カット開始! 3
来店 6
カット終了! 3
カット開始! 4
来店 7
来店 8
満席で立ち去る 8
カット終了! 4
カット開始! 6
カット終了! 6
カット開始! 7
カット終了! 7
Zzz...(-_-)
来店 9
Σ(゚_゚)
カット開始! 9
来店 10
カット終了! 9
カット開始! 10
来店 11
カット終了! 10
カット開始! 11
来店 12
来店 13
カット終了! 11
カット開始! 12
来店 14
来店 15
満席で立ち去る 15
カット終了! 12
カット開始! 13
来店 16
カット終了! 13
カット開始! 14
カット終了! 14
カット開始! 16
カット終了! 16
Zzz...(-_-)
※ 16人のうち13人をカット

ご意見・ご感想・ご指摘などありましたらよろしくお願いします!

*1:日本語の情報が皆無でちょっと苦労したので