1. 程式人生 > >Python之——協程

Python之——協程

協程(coroutine),又稱微執行緒,是一種使用者級的輕量級執行緒。協程擁有自己的暫存器上下文和棧。協程排程切換時,將暫存器上下文和棧儲存到其他地方,在切回來的時候,恢復先前儲存的暫存器上下文和棧。因此協程保留上一次呼叫時的狀態,每次過程重入時,就相當於進入上一次呼叫的狀態。在併發程式設計中,協程與執行緒類似,每個協程表示一個執行單元,有自己的本地資料,與其他協程共享全域性資料和其他資源。 協程需要使用者自己來編寫排程邏輯,對於CPU來說,協程其實是單執行緒,所以CPU不用去考慮怎麼排程、切換上下文,這就省去了CPU的切換開銷,所以協程在一定程度上又好於多執行緒。那麼在Python中是如何實現協程的呢? Python通過yield提供了對協程的基本支援,但是不完全,而使用第三方gevent庫是更好的選擇,gevent提供了比較完善的協程支援,是一個基於協程的Python網路函式庫,使用greenlet在libenv事件迴圈頂部提供了一個高級別併發性的API。 gevent對協程的支援,本質上是greenlet在實現切換工作。greenlet工作流程如下:假如進行訪問網路的IO操作時,出現阻塞,greenlet就顯示切換到另一段沒有被阻塞的程式碼執行,直到原先的阻塞狀況消失後,再自動切換回原來的程式碼繼續處理。因此,greenlet是一種合理安排的序列方式。 gevent使用流程程式碼如下:

# -*- coding:UTF-8 -*-

from gevent import monkey; monkey.patch_all()
import gevent
import urllib2

def run_task(url):
	print 'Visit --> %s' % url
	try:
		response = urllib2.urlopen(url)
		data = response.read()
		print '%d bytes received from %s.' % (len(data), url)
	except Exception, e:
		print e

if __name__ == '__main__':
	urls = ['https://github.com', 'https://www.python.org', 'http://www.cnblogs.com']
	greenlets = [gevent.spawn(run_task, url) for url in urls]
	gevent.joinall(greenlets)

以上程式主要用了gevent中的spawn方法和joinall方法。spawn方法可以看做是用來形成協程,joinall方法就是新增這些協程任務,並且啟動執行,從執行結果來看,3個網路操作是併發執行的,而且結束順序不同,但其實只有一個執行緒。

gevent中還提供了對池的支援。當擁有動態數量的greenlet需要進行併發管理(限制併發數)時,就可以使用池,這在處理大量的網路和IO操作時是非常需要的,接下來使用gevent中pool物件,來實現gevent操作。

示例程式碼如下:

# -*- coding:UTF-8 -*-
from gevent import monkey; monkey.patch_all()
import urllib2
from gevent.pool import Pool

def run_task(url):
	print 'Visit --> %s' % url
	try:
		response = urllib2.urlopen(url)
		data = response.read()
		print '%d biyes received from %s.' % (len(data), url)
	except Exception, e:
		print e
	return 'url:%s ---> finish' % url

if __name__ == '__main__':
	pool = Pool(2)
	urls = ['https://github.com', 'https://www.python.org', 'http://www.cnblogs.com']
	results = pool.map(run_task, urls)
	print results