輪流做事的工人
作者:gugod 發佈於:AnyEvent::Worker
是個把工作丟去其他行程執行,又超簡單而通用的的模組。對付「可能會花一段不定時間執行,
但此時並不需要等待它做完」的工作,特別有用。
基本上的使用方式如下:先建出工人物件($secretary
),提供個回呼描述一下做事方法,然後在有工作事項時,
調用其 do
方法。
my $secretary = AnyEvent::Worker->new(sub {
my ($task) = @_;
# 可能做很久
get_busy($task);
});
my @tasks = (
"my dirty laundry",
"send postcard to wife",
"drive my daughter back from school"
);
for (@tasks) {
$secretary->do($_, sub { ... # DONE })
}
AnyEvent::Worker
會自動分支出一個新的工人行程專門執行回呼中的程式碼。而其參數
(@tasks
)是從主行程,透過 socket 傳到工人行程的行程中。因此較建議用一般純量來表
示工作內容,而不要用物件。
上述範列程式中有三件事項要辨,在最後的 for
迴圈中依序丟給工人,此處的 do
會立
刻傳回,不會等到工作做完。使用時需要在之後的程式碼檢查工作是否完成。
而在分支出來工人行程中,這三件事情是一件接著一件做完的,如果目前工作還沒完畢,新
進的工作就會等著。AnyEvent::Worker
沒有提供自動分支出三支工人行程,平行完成事項
的機制。不過,如果工作項目有很多,原則上也不希望分支出爆量的工人行程,瞬間把資源
佔滿。大致上是希望,如果機器有 N 核心 CPU,就分支出 N 支工人,並將工作平均分配下
去。
那麼,要如何做到這點呢?
其實只要用 Data::RoundRobin
就可以簡單做到。這個模組將一列變數變一組,
可以輪流取出使用,取完後再從頭開始。例如:
my $r = Data::RoundRobin->new(qw(a b c));
say $r->next; #=> a
say $r->next; #=> b
say $r->next; #=> c
say $r->next; #=> a
say $r->next; #=> b
正好可以符合「平均分配工作」的概念。
以下建立四個工人,並且分配在同一組中,並將依次將工作分配工作到「下一位」工人:
my $secretaries = Data::RoundRobin->new(
map {
AnyEvent::Worker->new(sub{ ... } )
} 1..4
);
for my $task (...) {
$secretaries->next->do($task, sub { ... });
}
這麼一來,就會預先分支出四支工人行程,並依次接下工作。
這樣的組合十分簡單、好寫,但只能符合工作模式單純的情境。如果每項工作所需的時間可 能不定,先分配下去的工作有可能最後被做完,則需要更好的分散機制。以免工作都積在某 一個特定工人手上。另外也可讓工人行程視資源狀況自動增減,而不是一開始就預先分支等 等的需求也是存在的。關於這部份請直接參考如 Gearman 或 TheSchwartz 這類已經非常完整的解法。