#!/usr/bin/perl #============================================================================== # YosinoyaGyudooooon! #============================================================================== =head1 名前 よしのやぎゅどーん - ヨシノヤコピペをシミュレートする =head1 概要 コマンドラインから使う。 perl yosinoya.pl いくつかのオプションをつけることができる perl yosinoya.pl --shopkeeper=2 --visitor=50 =head1 説明 プログラムを開始すると店員が登場。続いて一定時間おきに客が来店。 注文をする。注文を受けると調理を開始。客に手渡す。食べ終わると 客は去る。各客ごとに注文してから料理が出されるまでの待ち時間を 計っているので、最後に全客の総計時間と、一人あたりの平均値、及び 一番待たされた客の時間が表示される。 それだけですよ? そ れ だ け。 =head1 オプション =over =item --shopkeeper 店員の数決定 (既定値は2) =item --visitor 客の総人数 (既定値は10) =item --openwait 開店してから店員が活動を開始するまでのタイムラグ(秒) (既定値は3) =item --interval 客が来る間隔(秒) (既定値は1) =item --capacity 店の容量。この値よりも多数客がいると新規の客はこない。 (既定値は10) =item --cooktime 一回の調理にかかる時間。1〜n秒のランダムな値をとる。(秒) (既定値は3) =back =head1 注意 Perl 5.8 以降でithreadsをサポートしている必要があります。 =head1 参考、触発 L 上記サイトの記事に出ていたエレベーターシミュレーターに触発されて つくりました。できたものは明後日の方向を向いてしまいましたが。 L,L,L =head1 著者 まかまか般若波羅蜜 =head1 著作権 Copyright (C) 2003 まかまか般若波羅蜜 このプログラムはPerlの規定と同じ条件のもと改造・再配布は自由に っていうか好きに使って。 =cut #============================================================================== $| = 1; use warnings; use strict; use threads; use threads::shared; use Thread::Queue; use Getopt::Long; use constant START => 1; use constant STOP => 0; #============================================================================== our $RUN : shared; # 店員スレッドの停止フラグ #============================================================================== # DEFAULT #============================================================================== use constant SHOP_KEEPER => 2; # 店員の数 use constant MAX_VISITOR =>10; # 客の数 use constant WAIT_OPEN => 3; # 開店してから注文を受け始めるまでのラグ use constant INTERVAL => 1; # 客が来る間隔 use constant CAPACITY =>10; # 店内の許容量 use constant COOKING_TIME => 3; # 調理時間 #============================================================================== # MAINROUTINE #============================================================================== main(); sub main{ my @keeper = (); # 店員スレッドを格納 my @visitor = (); # 客スレッドを格納 my $order = Thread::Queue->new; # 注文用キュー(各スレッドに渡す) my $opt = get_options(); # コマンドラインオプション my $sum_time; # 総待ち時間 init_all_class($opt); @keeper = init_shop_keeper( $order,$opt->{shopkeeper},$opt->{openwait},$opt->{cooktime}); $RUN = START; @visitor = init_visitor($order,$opt->{visitor},$opt->{interval}); { lock($Yosigyu::is_remained); cond_wait($Yosigyu::is_remained); # 全客終了シグナルを待つ } $RUN = STOP; $sum_time = finish(\@keeper,\@visitor); show_result($sum_time,$opt->{visitor}); } #============================================================================== # SUBROUTINE #============================================================================== sub get_options () { my $opt = {}; GetOptions( "shopkeeper=i" => \$opt->{shopkeeper}, "visitor=i" => \$opt->{visitor}, "openwait=i" => \$opt->{openwait}, "interval=i" => \$opt->{interval}, "capacity=i" => \$opt->{capacity}, "cooktime=i" => \$opt->{cooktime}, ); $opt->{shopkeeper} ||= SHOP_KEEPER, $opt->{visitor} ||= MAX_VISITOR, $opt->{openwait} ||= WAIT_OPEN, $opt->{interval} ||= INTERVAL, $opt->{capacity} ||= CAPACITY, $opt->{cooktime} ||= COOKING_TIME, return $opt; } #============================================================================== sub init_all_class ($) { my $opt = $_[0]; Yosigyu::init(); Yosigyu::counter( $opt->{visitor} ); Yosigyu::capacity( $opt->{capacity} ); Yosigyu::ShopKeeper::init(); Yosigyu::Visitor::init(); } #------------------------------------------------------------------------------ sub init_shop_keeper ($$$$) { my @keeper; my ($order,$num,$wait_open,$cooktime) = @_; while($num > 0){ push @keeper, threads->create(\&Yosigyu::ShopKeeper::run,$order,$wait_open,$cooktime); $num--; } return @keeper; } #------------------------------------------------------------------------------ sub init_visitor ($$$) { my @visitor; my ($order,$max,$interval) = @_; while($max > 0){ if( Yosigyu::is_filled() ){ sleep(1); next; } push @visitor, threads->create( \&Yosigyu::Visitor::run,$order); { lock($Yosigyu::SPACE); Yosigyu::space( Yosigyu::space() + 1 ); } sleep($interval); $max--; } return @visitor; } #------------------------------------------------------------------------------ sub finish ($$) { my ($ref_keeper,$ref_visitor) = @_; my $sum_time = 0; for( @{ $ref_visitor } ){ $sum_time += $_->join(); } for( @{ $ref_keeper } ){ $_->join(); } return $sum_time; } #------------------------------------------------------------------------------ sub show_result ($$) { my ($sum_time,$num) = @_; Yosigyu::closed(); print "\n"; print "総待ち時間\t$sum_time\t"; print "最大待ち時間\t$Yosigyu::Visitor::wait\n"; print "客一人あたりの平均待ち時間\t", sprintf("%.2f",$sum_time / $num) ,"\n"; } #============================================================================== #============================================================================== # 店 #============================================================================== package Yosigyu; use threads; use threads::shared; our $is_remained : shared; # 残り人数0の時にcond_signalするトリガー our $COUNT : shared; # 残り人数を表す our %ITEM : shared; # 注文の品が出来たかどうかのチェック our $SPACE : shared; # 店の空き my @menu; # 商品名のリスト my $CAPACITY; # 店の最大許容人数 #============================================================================== sub init{ $SPACE = 0; @menu = qw( 牛丼並 牛皿 牛鮭定食 牛丼大盛 ); } #------------------------------------------------------------------------------ sub menu{ return @menu; } #------------------------------------------------------------------------------ sub capacity (;$) { my $num = $_[0]; if(defined $num){ $CAPACITY = $num; } return $CAPACITY; } #------------------------------------------------------------------------------ sub counter(;$) : locked { my $num = $_[0]; if(defined $num){ $COUNT = $num; if($COUNT == 0){ lock($is_remained); cond_signal($is_remained); # 客が全員去ったら終了シグナル } } return $COUNT; } #------------------------------------------------------------------------------ sub space (;$) : locked { my $num = $_[0]; if(defined $num){ $SPACE = $num; } return $SPACE; } #------------------------------------------------------------------------------ sub is_filled{ return $SPACE >= capacity() ? 1 : 0; } #------------------------------------------------------------------------------ sub closed{ print "\n---- 閉店 ----\n"; } #============================================================================== #============================================================================== # 店員 #============================================================================== package Yosigyu::ShopKeeper; use strict; use threads; use threads::shared; use Thread::Queue; our $ACCEPT : shared; # 注文受け付け用ロック our $body : shared; # 店員ID #============================================================================== sub init{ $body = 1; } #============================================================================== sub run { my $self = Yosigyu::ShopKeeper->new(@_); my $order = $_[0]; sleep( ($self->{wait} ||= 1) ); while(!$order->pending){ last if(!$RUN); } while($RUN){ my $vid; { # 注文を受け付けるのは一度に一人だけ! lock($ACCEPT); $vid = $self->accept($order); } $vid ? $self->cook($vid) : sleep(1); } #print "shopkeeper" . $self->{id} . "exits\n"; } #------------------------------------------------------------------------------ sub new{ my $class = shift; my $self = {}; bless($self,$class); $self->_init(@_); return $self; } #------------------------------------------------------------------------------ sub _init{ my $self = shift; my ($order,$wait,$cooking_time) = @_; lock($body); $self->{id} = $body++; $self->{wait} = $wait; $self->{cooking_time} = $cooking_time; $self->say_hello(); } #------------------------------------------------------------------------------ sub say_hello{ my $self = shift; my $id = $self->{id}; print "店 員$id: 私が店員その$idです。\n"; } #------------------------------------------------------------------------------ sub cook{ my $self = shift; my $vid = $_[0]; my $id = $self->{id}; sleep( rand($self->{cooking_time}) + 1 ); print "店 員$id: はいお待ち>お客その$vid\n"; $Yosigyu::ITEM{$vid} = 0; $vid = 0; } #------------------------------------------------------------------------------ sub accept{ my $self = shift; my $id = $self->{id}; my $order = $_[0]; my ($vid,$item); if($order->pending){ $vid = $order->dequeue; $item = $Yosigyu::ITEM{$vid}; print "店 員$id: 注文承りました。>お客その$vid\n"; } return $vid; } #============================================================================== #============================================================================== # 訪問者 #============================================================================== package Yosigyu::Visitor; use threads; use threads::shared; use Thread::Queue; our $SIGNAL : shared; # 客間のシグナル our $body : shared; # 客ID our $wait : shared; # 最大待ち時間 #============================================================================== use constant P_THOUHT => 40; # 注文思案中の確率(%) use constant EATING_TIME_BASE => 2; # 基礎食事時間 use constant EATING_TIME_RAND => 2; # 食事時間プラスアルファ #============================================================================== sub init{ $body = 1; $wait = 0; } #------------------------------------------------------------------------------ sub signal_sender{ # これがつゆだくだ! return ("つゆだく",1); } #------------------------------------------------------------------------------ sub item{ # アクションの生成 return [ # メッセージと「つゆだく」シグナルの有無(0 or 1) ( map { [$_, ] } Yosigyu::menu() ), ["よーしパパ特盛頼んじゃうぞー", ], ["大盛りねぎだくギョク", ], [ (signal_sender) ], ]; } #============================================================================== # METHODS #============================================================================== sub run{ my $self = Yosigyu::Visitor->new; my $id = $self->{id}; my ($order) = @_; my $time; while(!$self->my_order){ # 注文するか、思案する sleep(1); if( rand(100) >= P_THOUHT ){ $self->send_order; } else { $self->thinking; } } $order->enqueue($id); # キューに入れて待ち時間の計測開始 $self->time_watch; if($self->{mysignal}){ # 自分のつゆだくシグナルに応答させないため sleep(1); # つゆだくシグナルを発したら1秒後に消す lock($SIGNAL); $SIGNAL = 0; } while($Yosigyu::ITEM{$id} != 0){ # 料理が出来るまで、 $self->check_signal(); # シグナルと空腹のチェック $self->check_hungry(); sleep(1); } $time = $self->time_watch; # 待ち時間計測終わり 最長記録かどうか確認 { lock($wait); $wait = $time > $wait ? $time : $wait; } $self->eat(); $self->say_goodbye(); # ごちそうさま return $time; } #------------------------------------------------------------------------------ sub new{ my $class = shift; my $self = {}; bless($self,$class); $self->_inti(); return $self; } #------------------------------------------------------------------------------ sub _inti{ my $self = shift; lock($body); $self->{id} = $body++; $self->{hungry} = 0; $self->say_hello(); } #------------------------------------------------------------------------------ sub say_hello{ my $self = shift; my $id = $self->{id}; my @hello = qw(ちは〜 こんにちは 腹減ったなぁ どうもぉ); my $mes = $hello[rand(@hello)]; print "客その$id: $mes ---- 客その$id 登場 ----\n"; } #------------------------------------------------------------------------------ sub thinking{ my $self = shift; my $id = $self->{id}; print "客その$id: 何にしようかなぁ…\n"; } #------------------------------------------------------------------------------ sub check_hungry{ # 7秒ごとに腹減ったコール my $self = shift; my $id = $self->{id}; my $hungry = ++$self->{hungry}; print "客その$id: ごはんまだ〜?\n" if($hungry > 0 and !($hungry % 7) ); } #------------------------------------------------------------------------------ sub check_signal{ my $self = shift; my $id = $self->{id}; my $item; lock($SIGNAL); if( $SIGNAL ){ $item = ( Yosigyu::Visitor::signal_sender )[0]; print "客その$id: お前は本当に$itemが頼みたいのかと>客その", $SIGNAL, "\n"; } $SIGNAL = 0; } #------------------------------------------------------------------------------ sub eat{ my $self = shift; my $id = $self->{id}; print "客その$id: いただきます…\n"; sleep(rand(EATING_TIME_RAND) + EATING_TIME_BASE); } #------------------------------------------------------------------------------ sub say_goodbye{ my $self = shift; my $id = $self->{id}; my $count; print "客その$id: ごちそうさま ---- 客その$id退場 ----\n"; { lock($COUNT); Yosigyu::counter( Yosigyu::counter() - 1 ); Yosigyu::space( Yosigyu::space() - 1 ); } } #------------------------------------------------------------------------------ sub send_order{ my $self = shift; my $id = $self->{id}; my $act = int( rand(10) ); my ($item,$sigflag); my $str = "客その$id: "; my $data = Yosigyu::Visitor::item(); my $rand = rand( scalar(@{$data}) ); ($item,$sigflag) = @{ $data->[$rand] }; print $str . $item . "\n"; $self->set_signal() if($sigflag); $self->{order} = 1; $Yosigyu::ITEM{$id} = $rand + 1; } #------------------------------------------------------------------------------ sub set_signal{ # $SIGNALにセット my $self = shift; lock($SIGNAL); $SIGNAL = $self->{id}; $self->{mysignal} = 1; } #------------------------------------------------------------------------------ sub my_order{ my $self = shift; return $self->{order}; } #------------------------------------------------------------------------------ sub time_watch{ my $self = shift; if(!$self->{time}){ $self->{time} = time; } else{ return time - $self->{time}; } } #------------------------------------------------------------------------------