銀月の符号

Python 使い見習いの日記・雑記

居眠り床屋問題解いたけれども

multiprocessing モジュールのマニュアル読みつつ、「居眠り床屋問題」をとりあえず解いた。けれども id:sumim さんからおそわった Axon.STM とかをつかってないので、もうしばらくこの問題とは付き合うことになりそう。要勉強。

# coding: utf-8

u"""居眠り床屋問題

http://ja.doukaku.org/285/
"""

from multiprocessing import \
        Process, Queue, Lock, Event, Array, current_process
from Queue import Empty
import time
import random

def barber(seats, output_data, barber_waking):
    pid = current_process().pid

    while True:
        try:
            seat_num, customer = seats.check()
        except SeatsEmpty:
            seat_num, customer = None, None
        if customer is not None:
            output_data.put(u"[barber %d] 散発開始 %d" % (pid, customer))
            time.sleep(random.randint(100, 400) / 1000.0)
            output_data.put(u"[barber %d] 散発終了 %d" % (pid, customer))
            seats.stand(seat_num)
        else:
            # 客がいないので寝る
            barber_waking.clear()
            output_data.put(u"[barber %d] 床屋、眠る" % pid)
            barber_waking.wait() # 寝た
            # 起こされた
            output_data.put(u"[barber %d] 床屋、目覚める" % pid)

def shop(seats, output_data, barber_waking):
    pid = current_process().pid

    for customer in  xrange(0, 16):
        try:
            output_data.put(u"[shop %d] 来店 %d" % (pid, customer))
            seats.sit(customer)
        except SeatsFull:
            output_data.put(u"[shop %d] 満席で立ち去る %d" % (
                pid, customer))
        else:
            if not barber_waking.is_set():
                output_data.put(u"[shop %d] 床屋を起こす %d" % (
                    pid, customer))
            barber_waking.set()
        if customer != 7:
            time.sleep(random.randint(0, 200) / 1000.0)
        else:
            time.sleep(random.randint(1200, 1400) / 1000.0)

class SeatsEmpty(Exception):
    pass

class SeatsFull(Exception):
    pass

class Seats(object):
    def __init__(self, num):
        # seats には客の id  (>= 0) が入る。負の値は空席を意味する
        self.seats = Array('b', num, lock=False)
        self.lock = Lock()
        with self.lock:
            for i in xrange(num):
                self.seats[i] = -1

    def check(self):
        u"""客がいるかどうか見る
        
        客がいなければ SeatsEmpty 例外を送出する。"""
        with self.lock:
            for seat_num, customer in enumerate(self.seats):
                if customer >= 0:
                    return seat_num, customer
            else:
                raise SeatsEmpty()

    def sit(self, customer):
        u"""客を空席に座らせる
        
        空席がない場合 SeatsFull 例外を送出する。"""
        with self.lock:
            for seat_num, seat in enumerate(self.seats):
                if seat < 0:
                    self.seats[seat_num] = customer
                    break
            else:
                raise SeatsFull()

    def stand(self, seat_num):
        u"""seat_num 番目の席に座っている客に席を立たせる"""
        with self.lock:
            assert self.seats[seat_num] >= 0
            self.seats[seat_num] = -1

def main():
    seats = Seats(3) # 席
    output_data = Queue() # 出力バッファ
    barber_waking = Event() # 床屋が起きているか
    barber_waking.set()

    process_shop = Process(
            target=shop,
            args=(seats, output_data, barber_waking))
    process_barber = Process(
            target=barber,
            args=(seats, output_data, barber_waking))

    process_barber.start()
    process_shop.start()

    while (process_shop.is_alive() or
           barber_waking.is_set() or
           not output_data.empty()):
        try:
            print output_data.get(timeout=1)
        except Empty:
            pass

    process_barber.terminate()
    process_barber.join()

if __name__ == '__main__':
    main()