標籤:
python中實現並發的方式有很多種,通過多進程並發可以真正利用多核資源,而多線程並發則實現了進程內資源的共用,然而Python中由於GIL的存在,多線程是沒有辦法真正實現多核資源的。
對於計算密集型程式,應該使用多進程並發充分利用多核資源,而在IO密集型程式中,多核優勢並不明顯,甚至由於大多數時間都是在IO堵塞狀態,多進程的切換消耗反而讓程式效率更加低下。
而當需要並發處理IO密集型任務時,就需要用到協程(Coroutine)。協程並沒有系統級的調度,而是使用者級的調度方式,避免了系統調用的開銷,雖然協程最終是串列工作,但是卻可以實現非常大的並發量。
參考文章:
https://blog.tonyseek.com/post/event-manage-with-greenlet/
producer-consumer
利用yield產生器,可以簡單展現協程的工作方式:
import time
def consumer():
print "Ready to receive"
while True:
y = (yield )
time.sleep(1)
print "Receive %s from producer”%y
def producer():
c = consumer()
c.next()
i = 1
while i > 0 and i < 11:
time.sleep(1)
print "Send %s to consumer"%i
c.send(i)
i += 1
if __name__ == ‘__main__‘:
producer()
上述過程展示了基本的生產者-消費者模型,消費者consumer是一個產生器;
當第一次在producer中調用c.next()時,啟用consumer,並且運行到yield時協程(consumer)被掛起,等待產生器被調用next或者send。
producer進行後續操作,並進入一個迴圈,每次暫停1s後,向產生器send一個訊息,消費者yield擷取到該訊息,並進行後續的工作。
可以看到,每次yield都需要等待send傳入的訊息之後才會繼續執行之後的任務。
通過yield實現協程
現在要來用yield真正建立一個協程了。
可以想象這樣一個模型,一個工地裡有很多相似的任務(jobs),並且會源源不斷產生這些任務,工地裡有一個工頭(foreman)負責,工頭為了分配任務給工人(worker),會制定一套流程(pipeline)來方便管理:分配工人,驗收工作(accept),由於工人工作(work)的時間遠遠大於分配任務的時間,將這些工人的工作(簡單枯燥的重複勞動)看成IO操作的話,這就是一個IO密集型的任務。下面看看python是如何通過yield來實現協程完成真箇工作的:
1 def main(): 2 foreman(args_of_overall,worker_num) 3 4 def foreman(args_of_overall,worker_num): 5 pipeline = create_pipeline(args_of_pipeline,worker_num) 6 for i,job in enumerate(get_jobs(args_of_ceate_jobs)): 7 worker_id = i % worker_num 8 pipeline.send((job,worker_id)) 9 10 @coroutine11 def worker(pipeline,accepting,job,my_id):12 while True:13 args_of_job, worker_id = (yield )14 if worker_id == my_id:15 result = work(args_of_job)16 accepting.send(result)17 elif pipeline is not None:18 pipeline.send((job,worker_id))19 20 @coroutine21 def accept():22 while True:23 result = (yield )24 #do_some_accepting25 26 def create_pipeline(args_of_pipeline,worker_num):27 pipeline = None28 accepting = accept()29 for work_id in range(work_num):30 pipeline = worker(pipeline,accepting,job,work_id)31 return pipeline32 33 def get_jobs(args_of_ceate_jobs):34 for job in job_source:35 yield job36 37 def coroutine(func):38 def warper(*args):39 f = func(*args)40 f.next()41 return f42 return warper43 44 def work(args_of_job):45 pass46 #do_some_work47 48 if __name__ == ‘__main__‘:49 main()
上述過程中,工人和驗收工作都是協程,而get_jobs()函數是一個產生器,當job是動態添加時,就可以改寫成一個協程,由此可見,產生器應該是一個特殊的協程。
上述所有的工作都是串列完成,雖然有很多工人,工人之間的工作是並發的(IO等待時間),但是工作一直是從第一個開始一個一個分配任務。
Python並發實踐_02_協程