用 AnyEvent 控制輸出量
作者:gugod 發佈於:在寫 IRC 機器人的時候一定會碰到要控制輸出訊息量的問題,不能一次爆量傳到 server 上,
不聽話 server 就會把機器人踢掉。目前所用的機器人是以 AnyEvent::IRC
來寫的,因此以
AnyEvent 為基礎,思考了幾個解法。
第一個解法最容易,就是有個定時器,固定每秒送一則訊息,而要送訊息時,則只是把訊息 內容塞到一個佇列中,不真的立刻送出,一個寫起來大致上像這樣:
my $bot = ...; # 假設是個 irc 機器人物件
my @queue;
my $timer = AE::timer 0, 1 sub {
return unless @queue;
$bot->send( shift @queue );
};
# 對外的介面
sub send_message {
push @queue, $_[0];
}
不過這個解法有個缺點,如果這個機器人基本上很閒,偶爾才比較忙一下,那 $timer
物
件會讓機器人每秒鐘做一次白工。如果能讓它「沒訊息時就不要再做白工」可能是比較理想的。
基於這樣的概念,於是第二個解法就出現了:
my $bot = ...;
my @queue;
my $timer;
sub send_message {
push @queue, $_[0];
$timer ||= AE::timer 0, 1 sub {
$bot->send( shift @queue );
undef $timer unless @queue;
};
}
稍微改一下名字就變得像是在做 Job Control:
sub work_for_real { ... }
my @job_queue;
my $job_timer;
sub work {
push @job_queue, $_[0];
$job_timer ||= AE::timer 0, 1, sub {
work_for_real( shift @job_queue );
undef $job_timer unless @job_queue > 0;
};
}
但還是有個問題,應該說,有個隱性的前提存在:這個工作內容(送出訊息)可以在一秒內
完成。如果不行,甚至超過很多的話,前述的 work_for_real
這個 sub-routine 就會有
重入(reentrant)的現象。
視實做而言,可能因此發生不好的競爭條件、或是死結(等等諸如此類在恐龍書裡面出
現的關鍵字)。
如果假設 work_for_real
本身的內容是同步型式,也就是在裡面基本上沒有留下其他 任
務未完成的 AnyEvent Watcher,那可以改寫成「前一件事做完了,再開始做下一件事」的架
構,看起來類似這樣(注意到 AE::timer
的參數與前例不同):
sub work_for_real { ... }
my @job_queue;
my $job_timer;
sub work_on_next_job {
return if @job_queue == 0;
work_for_real( shift @job_queue );
$job_timer = AE::timer 1, 0, sub {
undef $job_timer;
work_on_next_job;
};
}
sub work {
push @job_queue, $_[0];
work_on_next_job() unless $job_timer;
}
最後這種型式也比較有機會動態調整計時器的參數,比方說配合 @job_queue
增加的速度
來調整輸出的速度之類的。