標籤:
原創作品,轉載請註明出處:點我
在前兩篇文章中,我們介紹了什麼是Generator和coroutine,在這一篇文章中,我們會介紹coroutine在類比pipeline(管道 )和控制Dataflow(資料流)方面的運用。
coroutine可以用來類比pipeline行為。通過把多個coroutine串聯在一起來實現pipe,在這個管道中,資料是通過send()函數在各個coroutine之間傳遞的:
但是這些在pipe中傳遞的資料哪裡來的呢?這就需要一個資料來源,或者說producer.這個producer驅動整個pipe的運行:
通常情況下,source只是提供資料,驅動整個pipe的運行,其本身並不是一個coroutine,通常行為類似於下面這個模式:
其中,target就是一個coroutine,當調用target.send()函數的時候,資料將會傳入整個pipe。
既然pipeline有一個起點,同樣的也就必須要有一個sink(end-point,也就是終點)
sink是收集接受coroutine傳送過來的資料並對這些資料進行處理。sink通常的模式為:
在前面講述Generetor的文章中,我們用Generator實現了unix中的tail -f命令和tail -f | grep 命令,在這裡,我們也用coroutine來實現這兩個命令。
先來看看作為source的代碼unix_tail_f_co()函數
1 # A source that mimics Unix "tail -f" 2 def unix_tail_f_co(thefile, target): 3 ‘‘‘ 4 target是一個coroutine 5 ‘‘‘ 6 thefile.seek(0, 2) # 跳到檔案末尾 7 while True: 8 line = thefile.readline() 9 if not line:10 time.sleep(0.1)11 continue12 # 把資料發送給coroutine進行處理13 target.send(line)
在上面的代碼中,可以看到,target是一個coroutine,函數每次讀取一行資料,讀取到之後,就調用target.send()函數,把資料發送給了target,由target接收進行下一步的處理。
現在來看看作為sink的printer_co()函數,這個sink很簡單,就是簡單地列印它收到的資料。
1 # A sink just prints the lines2 @coroutine3 def printer_co():4 while True:5 # 在這個地方掛起,等待接收資料6 line = (yield)7 print line,
其中coroutine函數裝飾器使我們在上一篇介紹coroutine的文章中定義的。從代碼中可以看到,作為sink,print_co()函數有一個死迴圈,從第6行可以看到,在這個死迴圈中,
函數會一直掛起,等到資料的到來,然後每次接收到資料後,列印輸出,然後再次掛起等待資料的到來。
現在可以把上面兩個函數結合起來實現tail -f命令:
1 f = open("access-log")2 unix_tail_f_co(f,printer_co())
代碼首先開啟一個檔案f,f作為資料來源,把f和printer_co()傳遞給unix_tail_f_co(),這就實現了一個pipeline,只不過在這個pipeline中,資料是直接發送給作為sink的printer_co()函數的,中間沒有經過其他的coroutine。
在sink和source之間,可以根據需要,添加任何的coroutine,比如資料變換(transformation)、過濾(filter)和路由(routing)等
現在,我們添加一個coroutine,grep_filter_co(pattern,target),其中,target是一個coroutine
1 @coroutine2 def grep_filter_co(pattern,target):3 while True:4 # 掛起,等待接收資料5 line = (yield)6 if pattern in line:7 # 接收到的資料如果符合要求,8 # 則發送給下一個coroutine進行處理9 target.send(line)
從代碼中可以看到,grep_filter_co()有一個死迴圈,在迴圈中掛起等待接收資料,一旦接收到了資料,如果資料中存在pattern,則把接收到的資料發送給target,讓target對資料進行下一步處理,然後再次等待接收資料並掛起。
同樣的,現在把這三個函數組合起來,實現tail -f | grep命令,組成一個新的pipeline:
f = open("access-log")unix_tail_f_co(f,grep_filter_co("python",printer_co()))
unix_tail_f_co()作為source,從f檔案處每次讀取一行資料,發送給grep_filter_co()這個coroutine,grep_filer_co()對接收到的資料進行過濾(filter):如果接收到的資料包含了"python"這個單詞,就把資料發送給printer_co()進行處理,然後source再把下一行資料發送到pipeline中進行處理。
在前面也用Generator實現了tail -f | grep 命令,現在可以把兩者做一個比較:
Generator實現的流程為:
而coroutine實現的流程為:
可以看出,Generator 在最後的的迭代過程中從pipe中擷取資料,而coroutine通過send()函數把資料發送到pipeline中去。
通過coroutine,可以把資料發送到不同的目的地,如:
下面我們來實現訊息廣播(Broadcasting)機制,首先要先定義一個函數broadcast_co(targets)
1 # 把資料發送給多個不同的coroutine2 @coroutine3 def broadcast_co(targets):4 while True:5 # 掛起,等待接收資料6 item = (yield)7 for target in targets:8 # 接收到了資料,然後分別發送給不同的coroutine9 target.send(item)
broadcats_co()函數接受一個參數targets,這個參數是一個列表(list),其中的每一個成員都是coroutine,在一個死迴圈中,函數接收到資料之後,把資料依次發送給不同的coroutine進行處理,然後會掛起等待接收資料。
f = open("access-log")unix_tail_f_co(f, broadcast_co([grep_filter_co(‘python‘,printer_co()), grep_filter_c0(‘ply‘,printer_co()), grep_filter_co(‘swig‘,printer_co())]) )
unix_tail_f_co每次從f讀取一行資料,發送給broadcast_co(),broadcast_co()會把接收到的資料依次發送給gerp_filter_co(),每個grep_filter_co()再會把資料發送給相應的printer_co()進行處理。
|---------------> grep_filter_co("python") ------> printer_co() unix_tail_f_co()--->broadcast_co() ----> grep_filter_co("ply") ---------> printer_co() |---------------> grep_filter_co("swig")---------> printer_co()
需要注意的是:broadcast_co()會先把資料發送給grep_filter_co("python"),grep_filter_co("python")會把資料發送給printer_co(),當printer_co()執行後掛起再次等待接受資料時,執行權返回到grep_filter_co("python")函數,此時grep_filter_co("python")也會掛起等待接收資料,執行權回到broadcast_co()函數,此時broadcast_co()才會把訊息發送給grep_filter_co("ply"),也只有當grep_filter_co("ply")執行完畢掛起之後,broadcast_co()才會接著把資料發送給下一個coroutine。
如果把上面的代碼改成這樣,就會有另外一種broadcast的模式:
f = open("access-log")p = printer_co()unix_tail_f_co(f, broadcast_co([grep_filter_co(‘python‘,p)), grep_filter_co(‘ply‘,p), grep_filter_co(‘swig‘,p)]) )
此時,broadcast的模式為
|---------------> grep_filter_co("python") ---------->|unix_tail_f_co()--->broadcast_co() ----> grep_filter_co("ply") ---------> printer_co() |---------------> grep_filter_co("swig")------------->|
最後資料都會傳送到同一個print_co()函數,也就是說最後資料的目的地為同一個。
好了,這篇講解coroutine在類比pipeline和控制dataflow上的應用已經完畢了,可以看出coroutine在資料路由方面有很強大的控制能力,可以把多個不同的處理方式組合在一起使用。
下一篇文章會講解如何用coroutine是下班一個簡單的多任務(Multitask)的作業系統,盡請期待O(∩_∩)O。
Python進階編程之產生器(Generator)與coroutine(二):coroutine與pipeline(管道)和Dataflow(資料流_