什麼是面向事件的編程(事件驅動的編程):
編程中所有的程式是由事件決定 – 可以是由使用者操作(鍵盤,滑鼠),也可以是由其他程式和流的到達或者作業系統事件(如網路資料包到達)來觸發執行.
面向事件編程可以也被定義為,寫一個電腦程式,在其中的代碼(通常程式的功能的頭部)被明確分配應用程式的主迴路,其代碼本身由兩部分組成方法:事件和事件處理的代碼。
面向事件的編程通常被應用在三種情況下:
1.建立使用者介面的控制(包括圖形)
2.建立一個基於伺服器的應用程式
3.遊戲編程時多個對象的管理
我們系統管理時,這種應用在伺服器的應用程式中使用面向事件的編程很多,比如用於伺服器應用解決10,000個並發串連(所謂 C10k 問題)
AnyEvent 是一個效能非常好的基於事件驅動的程式,有人使用它來解決 C10k 的問題,象平時我們寫的程式,都是基於過程.我們都是先做完事件1-> 然後做事件2->然後做事件3 .這種方式.
但基於事件就完全不一樣了,在主流程中你基本只有一個主體架構,程式的動作觸發都是由事件來驅動.比如我們使用的視窗程序.點最大化最小化,都是基於事件,當接收到了最大化的事件做最大化事件那部分的程式開始運行.不在從頭到尾部來執行.所以我們讀基於事件的程式,最好是畫成思維導圖來協助我們理解.
基於事件的程式常用到的最大好處是用來做非同步,例如,我們要下載 100 個檔案,下載完後對這些檔案進行處理.可能給每個下載和處理的過程寫成事件,這些事件可以同步運行(關鍵在於網路連接和進行檔案的讀寫 IO 時要等待,事件是給這些等待覆用起來).
不知大家瞭解 Perl 中的 select 這個功能不,就是等到控制代碼可以讀或者寫的時候,做不同的讀或者寫的操作.事件迴圈也是一樣.
在整個 AnyEvent 入門中,我們只要關注二個點就行, WATCHERS(監控者) 和 條件變數.
WATCHERS(監控者)
在 select 中,有個角色叫"監控者",就是 select 函數本身.
在 AnyEvent 中不但可以監控 IO 還可以監控別的一些事件.來做不同的處理.我們可以看成這是不斷的盯著某件事情的人
有如下幾個基本的內建的可以用來盯著的事情("監控者").
TIMER : 監控時間,到了一定的條件,然後對不同的時間做不同的事件
I/O: 這個是監控到 IO 是否可以讀寫,然後做相應的事件
IDLE: 空閑時做什麼事件
SIGNAL : 監控觀查到不同的資訊,調用相應的事件
CHILD PROCESS: 對子程式的狀態來調用相應的處理事件
TIMER WATCHERS
基本文法
複製代碼 代碼如下:
AnyEvent->timer(
after => $seconds, # 多久之後做相應的操作.
interval => $seconds, # 在上麵條件生效後,每格多久進行一次 callback.
cb => $cb, # cb 是 callback 的簡寫,所以知道了吧,只要到了前面的條件,就會運行 cb => 指向的函數.
);
使用執行個體:
下面的例子是,5 秒後,每 2 秒進行一次 callback 中的事件,直到 $w 這個註冊的事件被 undef 為止(也就是 $count > 10 次).這個中的 undef $w 是取消掉這種 watcher 的方法.
複製代碼 代碼如下:
#!/usr/bin/perl
use strict;
use AnyEvent;
my $cv = AnyEvent->condvar;
my $count = 0;
my $w; $w = AnyEvent->timer(
after => 5,
interval => 2,
cb => sub {
$count++;
warn "這是第 $count 次調用";
if ($count >= 10) {
undef $w;
}
}
);
$cv->recv;
I/O WATCHERS
基本文法
複製代碼 代碼如下:
my $fh = ....; # 開啟一個控制代碼
my $io; $io = AnyEvent->io(
fh => $fh, # 上面開啟的控制代碼,也可以是標準輸入和輸出
poll => "w", # 這個地方可以選擇 r 和 w 來表示讀和寫的 IO 事件
cb => sub {
syswrite( $fh, "寫入的內容" );
undef $io;
}
);
使用執行個體:
下面的例子,是使用 io 監控到可以讀,就調用 cb 的函數,直接讀檔案 test.txt,每次一個位元組,直到讀完這個檔案就通過 undef 消掉這個事件.
複製代碼 代碼如下:
#!/usr/bin/perl
use strict;
use AnyEvent;
my $cv = AnyEvent->condvar;
open my $fh, "<test.txt" or die "不能開啟檔案控制代碼 $!";
my $io; $io = AnyEvent->io(
fh => $fh,
poll => "r",
cb => sub {
my $len = sysread( $fh, my $buf, 1 );
if ($len > 0) {
print "read '$buf'\n";
}
else {
undef $io;
die "讀出錯: $!";
}
});
$cv->recv;
IDLE WATCHERS
基本文法
複製代碼 代碼如下:
my $w = AnyEvent->idle (cb => sub { ... });
使用執行個體:
下面的例子,當整個程式中,沒有其它事件在運行時,就會運行 idle .它就是當其它事件都在等待和空著的時候,所調用的.
複製代碼 代碼如下:
#!/usr/bin/perl
use strict;
use AnyEvent;
my $cv = AnyEvent->condvar;
my $t; $t = AnyEvent->timer(
after => 1,
interval => 1,
cb => sub { print time()."\n" }
);
my $w; $w = AnyEvent->idle(
cb => sub {
warn "idle";
# undef $w;
}
);
$cv->recv;
SIGNAL WATCHERS
基本文法如下,就是當接收到 POSIX signal 的時候,運行 callback 中的事件.
複製代碼 代碼如下:
my $w = AnyEvent->signal (signal => "TERM", cb => sub { ... });
CHILD PROCRSS WATCHERS
基本文法如下
複製代碼 代碼如下:
# child process exit
my $w = AnyEvent->child (pid => $pid, cb => sub {
my ($pid, $status) = @_;
...
});
條件變數(多個條件時)
這個是 AnyEvent 學習上面幾種事件監控後必須要瞭解的.大家都見到上面有 AnyEvent->condvar; 和 $cv->recv這二個,condvar 是 condition variable 的簡寫.是指當什麼樣的條件成立時的變數
其實就是條件,當達到什麼條件時退出事件迴圈.所以 AnyEvent 中沒有傳統事件中的 loop 函數.所以使用條件變數就相當於讓事件這個轉起來.
基本的 $cv->recv 是和 $cv->send 成對出現的,當事件調用 send 時,就一定要有 recv 收到這個調用,才會退出事件.
下面的 $cv->begin 和 $cv->end 也基本是這個意思.send 是單個條件.begin 和 end 是多個條件成立時退出,換個語來講,就是這些事件都成對的完成後,才退出事件.
複製代碼 代碼如下:
#!/usr/bin/perl
use strict;
use AnyEvent;
my $cv = AnyEvent->condvar( cb => sub {
warn "調用結束";
});
for my $i (1..10) {
$cv->begin;
my $w; $w = AnyEvent->timer(after => $i, cb => sub {
warn "finished timer $i";
undef $w;
$cv->end;
});
}
$cv->recv;
預設的 condvar 會對事件建一個條件為假的變數,所以直接有 send 和 begin send 之類才會變成真,然後退出事件迴圈.可以給這個地方看成一個訊號量來理解就好了.y
如果條件不成立,在 AnyEvent 中事件會一直 loop .所以上面的例子中沒有 send .
有關 AnyEvent 其它,大家入門後可以玩玩象 AnyEvent::HTTP,twiggy 之類.看看這些應用和項目.
另外,在 AnyEvent 中我們常常使用 EV .他是一個 C 的 libev 的 Perl 介面,有非常高的效能.看完上面,在看看下面 EV 的使用,非常容易吧,基本不變.只是沒出現條件變數,
使用的傳統的 EV::loop; 來使這個運行起來.
複製代碼 代碼如下:
use EV;
# TIMERS
my $w = EV::timer 2, 0, sub {
warn "is called after 2s";
};
my $w = EV::timer 2, 2, sub {
warn "is called roughly every 2s (repeat = 2)";
};
undef $w; # destroy event watcher again
my $w = EV::periodic 0, 60, 0, sub {
warn "is called every minute, on the minute, exactly";
};
# IO
my $w = EV::io *STDIN, EV::READ, sub {
my ($w, $revents) = @_; # all callbacks receive the watcher and event mask
warn "stdin is readable, you entered: ", <STDIN>;
};
# SIGNALS
my $w = EV::signal 'QUIT', sub {
warn "sigquit received\n";
};
# CHILD/PID STATUS CHANGES
my $w = EV::child 666, 0, sub {
my ($w, $revents) = @_;
my $status = $w->rstatus;
};
# STAT CHANGES
my $w = EV::stat "/etc/passwd", 10, sub {
my ($w, $revents) = @_;
warn $w->path, " has changed somehow.\n";
};
# MAINLOOP
EV::loop; # loop until EV::unloop is called or all watchers stop
EV::loop EV::LOOP_ONESHOT; # block until at least one event could be handled
EV::loop EV::LOOP_NONBLOCK; # try to handle same events, but do not block
註:本文中大部分內容來自日本的@lestrrat