Python 協(xié)程:概念及其用法

協(xié)程

概念

協(xié)程亿眠,又稱微線程碎罚,纖程,英文名Coroutine纳像。協(xié)程的作用荆烈,是在執(zhí)行函數(shù)A時,可以隨時中斷,去執(zhí)行函數(shù)B憔购,然后中斷繼續(xù)執(zhí)行函數(shù)A(可以自由切換)宫峦。但這一過程并不是函數(shù)調(diào)用(沒有調(diào)用語句),這一整個過程看似像多線程玫鸟,然而協(xié)程只有一個線程執(zhí)行导绷。

優(yōu)勢

  • 執(zhí)行效率極高,因為子程序切換(函數(shù))不是線程切換屎飘,由程序自身控制诵次,沒有切換線程的開銷。所以與多線程相比枚碗,線程的數(shù)量越多,協(xié)程性能的優(yōu)勢越明顯铸本。

  • 不需要多線程的鎖機(jī)制肮雨,因為只有一個線程,也不存在同時寫變量沖突箱玷,在控制共享資源時也不需要加鎖怨规,因此執(zhí)行效率高很多。

說明:協(xié)程可以處理IO密集型程序的效率問題锡足,但是處理CPU密集型不是它的長處波丰,如要充分發(fā)揮CPU利用率可以結(jié)合多進(jìn)程+協(xié)程。

以上只是協(xié)程的一些概念舶得,可能聽起來比較抽象掰烟,那么我結(jié)合代碼講一講吧。這里主要介紹協(xié)程在Python的應(yīng)用沐批,Python2對協(xié)程的支持比較有限纫骑,生成器的yield實現(xiàn)了一部分但不完全,gevent模塊倒是有比較好的實現(xiàn)九孩;Python3.4以后引入了asyncio模塊先馆,可以很好的使用協(xié)程。

Python2.x協(xié)程

python2.x協(xié)程應(yīng)用:

  • yield

  • gevent

python2.x中支持協(xié)程的模塊不多躺彬,gevent算是比較常用的煤墙,這里就簡單介紹一下gevent的用法。

Gevent

gevent是第三方庫宪拥,通過greenlet實現(xiàn)協(xié)程仿野,其基本思想:

當(dāng)一個greenlet遇到IO操作時,比如訪問網(wǎng)絡(luò)江解,就自動切換到其他的greenlet设预,等到IO操作完成,再在適當(dāng)?shù)臅r候切換回來繼續(xù)執(zhí)行犁河。由于IO操作非常耗時鳖枕,經(jīng)常使程序處于等待狀態(tài)魄梯,有了gevent為我們自動切換協(xié)程,就保證總有g(shù)reenlet在運(yùn)行宾符,而不是等待IO酿秸。

Install

pip install gevent

最新版貌似支持windows了,之前測試好像windows上運(yùn)行不了……

Usage

首先來看一個簡單的爬蟲例子:

! -*- coding:utf-8 -*-

import gevent

from gevent import monkey;monkey.patch_all()

import urllib2

def get_body(i):

print "start",i

urllib2.urlopen

print "end",i

tasks=[gevent.spawn(get_body,i) for i in range(3)]

gevent.joinall(tasks)

運(yùn)行結(jié)果:

start 0

start 1

start 2

end 2

end 0

end 1

說明:從結(jié)果上來看魏烫,執(zhí)行g(shù)et_body的順序應(yīng)該先是輸出”start”辣苏,然后執(zhí)行到urllib2時碰到IO堵塞,則會自動切換運(yùn)行下一個程序(繼續(xù)執(zhí)行g(shù)et_body輸出start)哄褒,直到urllib2返回結(jié)果稀蟋,再執(zhí)行end。也就是說呐赡,程序沒有等待urllib2請求網(wǎng)站返回結(jié)果退客,而是直接先跳過了,等待執(zhí)行完畢再回來獲取返回值链嘀。值得一提的是萌狂,在此過程中,只有一個線程在執(zhí)行怀泊,因此這與多線程的概念是不一樣的茫藏。

換成多線程的代碼看看:

import threading

import urllib2

def get_body(i):

print "start",i

urllib2.urlopen

print "end",i

for i in range(3):

t=threading.Thread(target=get_body,args=(i,))

t.start()

運(yùn)行結(jié)果:

start 0

start 1

start 2

end 1

end 2

end 0

說明:從結(jié)果來看,多線程與協(xié)程的效果一樣霹琼,都是達(dá)到了IO阻塞時切換的功能务傲。不同的是,多線程切換的是線程(線程間切換)碧囊,協(xié)程切換的是上下文(可以理解為執(zhí)行的函數(shù))树灶。而切換線程的開銷明顯是要大于切換上下文的開銷,因此當(dāng)線程越多糯而,協(xié)程的效率就越比多線程的高天通。(猜想多進(jìn)程的切換開銷應(yīng)該是最大的)

Gevent使用說明

  • monkey可以使一些阻塞的模塊變得不阻塞,機(jī)制:遇到IO操作則自動切換熄驼,手動切換可以用gevent.sleep(0)(將爬蟲代碼換成這個像寒,效果一樣可以達(dá)到切換上下文)

  • gevent.spawn 啟動協(xié)程,參數(shù)為函數(shù)名稱瓜贾,參數(shù)名稱

  • gevent.joinall 停止協(xié)程

Python3.x協(xié)程

為了測試Python3.x下的協(xié)程應(yīng)用诺祸,我在virtualenv下安裝了python3.6的環(huán)境。

python3.x協(xié)程應(yīng)用:

  • asynico + yield from(python3.4)

  • asynico + await(python3.5)

  • gevent

Python3.4以后引入了asyncio模塊祭芦,可以很好的支持協(xié)程筷笨。

asynico

asyncio是Python 3.4版本引入的標(biāo)準(zhǔn)庫,直接內(nèi)置了對異步IO的支持。asyncio的異步操作胃夏,需要在coroutine中通過yield from完成轴或。

Usage

例子:(需在python3.4以后版本使用)

import asyncio

@asyncio.coroutine

def test(i):

print("test_1",i)

r=yield from asyncio.sleep(1)

print("test_2",i)

loop=asyncio.get_event_loop()

tasks=[test(i) for i in range(5)]

loop.run_until_complete(asyncio.wait(tasks))

loop.close()

運(yùn)行結(jié)果:

test_1 3

test_1 4

test_1 0

test_1 1

test_1 2

test_2 3

test_2 0

test_2 2

test_2 4

test_2 1

說明:從運(yùn)行結(jié)果可以看到,跟gevent達(dá)到的效果一樣仰禀,也是在遇到IO操作時進(jìn)行切換(所以先輸出test_1照雁,等test_1輸出完再輸出test_2)。但此處我有一點不明答恶,test_1的輸出為什么不是按照順序執(zhí)行的呢饺蚊?可以對比gevent的輸出結(jié)果(希望大神能解答一下)。

asyncio說明

@asyncio.coroutine把一個generator標(biāo)記為coroutine類型悬嗓,然后污呼,我們就把這個coroutine扔到EventLoop中執(zhí)行。

test()會首先打印出test_1包竹,然后曙求,yield from語法可以讓我們方便地調(diào)用另一個generator。由于asyncio.sleep()也是一個coroutine映企,所以線程不會等待asyncio.sleep(),而是直接中斷并執(zhí)行下一個消息循環(huán)静浴。當(dāng)asyncio.sleep()返回時堰氓,線程就可以從yield from拿到返回值(此處是None),然后接著執(zhí)行下一行語句苹享。

把a(bǔ)syncio.sleep(1)看成是一個耗時1秒的IO操作双絮,在此期間,主線程并未等待得问,而是去執(zhí)行EventLoop中其他可以執(zhí)行的coroutine了囤攀,因此可以實現(xiàn)并發(fā)執(zhí)行。

asynico/await

為了簡化并更好地標(biāo)識異步IO宫纬,從Python 3.5開始引入了新的語法async和await焚挠,可以讓coroutine的代碼更簡潔易讀。

請注意漓骚,async和await是針對coroutine的新語法蝌衔,要使用新的語法,只需要做兩步簡單的替換:

  • 把@asyncio.coroutine替換為async蝌蹂;

  • 把yield from替換為await噩斟。

Usage

例子(python3.5以后版本使用):

import asyncio

async def test(i):

print("test_1",i)

await asyncio.sleep(1)

print("test_2",i)

loop=asyncio.get_event_loop()

tasks=[test(i) for i in range(5)]

loop.run_until_complete(asyncio.wait(tasks))

loop.close()

運(yùn)行結(jié)果與之前一致。

說明:與前一節(jié)相比孤个,這里只是把yield from換成了await剃允,@asyncio.coroutine換成了async,其余不變。

gevent

同python2.x用法一樣斥废。

協(xié)程VS多線程

如果通過以上介紹椒楣,你已經(jīng)明白多線程與協(xié)程的不同之處,那么我想測試也就沒有必要了营袜。因為當(dāng)線程越來越多時撒顿,多線程主要的開銷花費(fèi)在線程切換上,而協(xié)程是在一個線程內(nèi)切換的荚板,因此開銷小很多凤壁,這也許就是兩者性能的根本差異之處吧。(個人觀點)

異步爬蟲

也許關(guān)心協(xié)程的朋友跪另,大部分是用其寫爬蟲(因為協(xié)程能很好的解決IO阻塞問題)拧抖,然而我發(fā)現(xiàn)常用的urllib、requests無法與asyncio結(jié)合使用免绿,可能是因為爬蟲模塊本身是同步的(也可能是我沒找到用法)唧席。那么對于異步爬蟲的需求,又該怎么使用協(xié)程呢嘲驾?或者說怎么編寫異步爬蟲淌哟?

給出幾個我所了解的方案:

  • grequests (requests模塊的異步化)

  • 爬蟲模塊+gevent(比較推薦這個)

  • aiohttp (這個貌似資料不多,目前我也不太會用)

  • asyncio內(nèi)置爬蟲功能 (這個也比較難用)

協(xié)程池

作用:控制協(xié)程數(shù)量

from bs4 import BeautifulSoup

import requests

import gevent

from gevent import monkey, pool

monkey.patch_all()

jobs = []

links = []

p = pool.Pool(10)

urls = [

# ... another 100 urls

]

def get_links(url):

r = requests.get(url)

if r.status_code == 200:

soup = BeautifulSoup(r.text)

links + soup.find_all('a')

for url in urls:

jobs.append(p.spawn(get_links, url))

gevent.joinall(jobs)我有建立一個python學(xué)習(xí)交流群辽故,在群里我們相互幫助徒仓,相互關(guān)心,相互分享內(nèi)容誊垢,這樣出問題幫助你的人就比較多掉弛,群號是301,還有056喂走,最后是069殃饿,這樣就可以找到大神聚合的群,如果你只愿意別人幫助你芋肠,不愿意分享或者幫助別人乎芳,那就請不要加了,你把你會的告訴別人這是一種分享帖池。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秒咐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子碘裕,更是在濱河造成了極大的恐慌携取,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帮孔,死亡現(xiàn)場離奇詭異雷滋,居然都是意外死亡不撑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門晤斩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來焕檬,“玉大人,你說我怎么就攤上這事澳泵∈涤蓿” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵兔辅,是天一觀的道長腊敲。 經(jīng)常有香客問我,道長维苔,這世上最難降的妖魔是什么碰辅? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮介时,結(jié)果婚禮上没宾,老公的妹妹穿的比我還像新娘。我一直安慰自己沸柔,他們只是感情好循衰,可當(dāng)我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著褐澎,像睡著了一般羹蚣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乱凿,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機(jī)與錄音咽弦,去河邊找鬼徒蟆。 笑死,一個胖子當(dāng)著我的面吹牛型型,可吹牛的內(nèi)容都是我干的段审。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼闹蒜,長吁一口氣:“原來是場噩夢啊……” “哼寺枉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起绷落,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤姥闪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后砌烁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體筐喳,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡催式,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了避归。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荣月。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖梳毙,靈堂內(nèi)的尸體忽然破棺而出哺窄,到底是詐尸還是另有隱情,我是刑警寧澤账锹,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布萌业,位于F島的核電站,受9級特大地震影響牌废,放射性物質(zhì)發(fā)生泄漏咽白。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一鸟缕、第九天 我趴在偏房一處隱蔽的房頂上張望晶框。 院中可真熱鬧,春花似錦懂从、人聲如沸授段。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侵贵。三九已至,卻和暖如春缘薛,著一層夾襖步出監(jiān)牢的瞬間窍育,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工宴胧, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留漱抓,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓恕齐,卻偏偏與公主長得像乞娄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子显歧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容