Python學(xué)習(xí)(12)—協(xié)程

1、概念

協(xié)程躲舌,又稱微線程丑婿,纖程,英文名Coroutine没卸。協(xié)程的作用羹奉,是在執(zhí)行函數(shù)A時(shí),可以隨時(shí)中斷约计,去執(zhí)行函數(shù)B诀拭,然后中斷繼續(xù)執(zhí)行函數(shù)A(可以自由切換)。但這一過(guò)程并不是函數(shù)調(diào)用(沒(méi)有調(diào)用語(yǔ)句)煤蚌,這一整個(gè)過(guò)程看似像多線程耕挨,然而協(xié)程只有一個(gè)線程執(zhí)行。

優(yōu)勢(shì)

  • 執(zhí)行效率極高尉桩,因?yàn)樽映绦蚯袚Q(函數(shù))不是線程切換筒占,由程序自身控制,沒(méi)有切換線程的開銷蜘犁。所以與多線程相比翰苫,線程的數(shù)量越多,協(xié)程性能的優(yōu)勢(shì)越明顯沽瘦。
  • 不需要多線程的鎖機(jī)制革骨,因?yàn)橹挥幸粋€(gè)線程农尖,也不存在同時(shí)寫變量沖突,在控制共享資源時(shí)也不需要加鎖良哲,因此執(zhí)行效率高很多盛卡。

說(shuō)明:協(xié)程可以處理IO密集型程序的效率問(wèn)題,但是處理CPU密集型不是它的長(zhǎng)處筑凫,如要充分發(fā)揮CPU利用率可以結(jié)合多進(jìn)程+協(xié)程滑沧。

以上只是協(xié)程的一些概念,可能聽起來(lái)比較抽象巍实,那么我結(jié)合代碼講一講吧滓技。這里主要介紹協(xié)程在Python的應(yīng)用,Python2對(duì)協(xié)程的支持比較有限棚潦,生成器的yield實(shí)現(xiàn)了一部分但不完全令漂,gevent模塊倒是有比較好的實(shí)現(xiàn);Python3.4以后引入了asyncio模塊丸边,可以很好的使用協(xié)程叠必。

2、Python2.x協(xié)程

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

  • yield
  • gevent

python2.x中支持協(xié)程的模塊不多妹窖,gevent算是比較常用的纬朝,這里就簡(jiǎn)單介紹一下gevent的用法。

Gevent

gevent是第三方庫(kù)骄呼,通過(guò)greenlet實(shí)現(xiàn)協(xié)程共苛,其基本思想:
 當(dāng)一個(gè)greenlet遇到IO操作時(shí),比如訪問(wèn)網(wǎng)絡(luò)蜓萄,就自動(dòng)切換到其他的greenlet隅茎,等到IO操作完成,再在適當(dāng)?shù)臅r(shí)候切換回來(lái)繼續(xù)執(zhí)行绕德。由于IO操作非常耗時(shí)患膛,經(jīng)常使程序處于等待狀態(tài),有了gevent為我們自動(dòng)切換協(xié)程耻蛇,就保證總有g(shù)reenlet在運(yùn)行,而不是等待IO胞此。

Install

pip install gevent
最新版貌似支持windows了臣咖,之前測(cè)試好像windows上運(yùn)行不了……

Usage

首先來(lái)看一個(gè)簡(jiǎn)單的爬蟲例子:

#! -*- coding:utf-8 -*-
import gevent
from gevent import monkey;monkey.patch_all()
import urllib2
def get_body(i):
    print "start",i
    urllib2.urlopen("http://cn.bing.com")
    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

說(shuō)明:從結(jié)果上來(lái)看,執(zhí)行g(shù)et_body的順序應(yīng)該先是輸出”start”漱牵,然后執(zhí)行到urllib2時(shí)碰到IO堵塞夺蛇,則會(huì)自動(dòng)切換運(yùn)行下一個(gè)程序(繼續(xù)執(zhí)行g(shù)et_body輸出start),直到urllib2返回結(jié)果酣胀,再執(zhí)行end刁赦。也就是說(shuō)娶聘,程序沒(méi)有等待urllib2請(qǐng)求網(wǎng)站返回結(jié)果,而是直接先跳過(guò)了甚脉,等待執(zhí)行完畢再回來(lái)獲取返回值丸升。值得一提的是,在此過(guò)程中牺氨,只有一個(gè)線程在執(zhí)行狡耻,因此這與多線程的概念是不一樣的。
換成多線程的代碼看看:

import threading
import urllib2
def get_body(i):
    print "start",i
    urllib2.urlopen("http://cn.bing.com")
    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

說(shuō)明:從結(jié)果來(lái)看猴凹,多線程與協(xié)程的效果一樣夷狰,都是達(dá)到了IO阻塞時(shí)切換的功能。不同的是郊霎,多線程切換的是線程(線程間切換)沼头,協(xié)程切換的是上下文(可以理解為執(zhí)行的函數(shù))。而切換線程的開銷明顯是要大于切換上下文的開銷书劝,因此當(dāng)線程越多瘫证,協(xié)程的效率就越比多線程的高。(猜想多進(jìn)程的切換開銷應(yīng)該是最大的)

Gevent使用說(shuō)明
  • monkey可以使一些阻塞的模塊變得不阻塞庄撮,機(jī)制:遇到IO操作則自動(dòng)切換背捌,手動(dòng)切換可以用gevent.sleep(0)(將爬蟲代碼換成這個(gè),效果一樣可以達(dá)到切換上下文)
  • gevent.spawn 啟動(dòng)協(xié)程洞斯,參數(shù)為函數(shù)名稱毡庆,參數(shù)名稱
  • gevent.joinall 停止協(xié)程

3、Python3.x協(xié)程

python3.5協(xié)程使用可以移步:Python3.5協(xié)程學(xué)習(xí)研究

為了測(cè)試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)庫(kù)蝇刀,直接內(nèi)置了對(duì)異步IO的支持。asyncio的異步操作徘溢,需要在coroutine中通過(guò)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

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

asyncio說(shuō)明

@asyncio.coroutine把一個(gè)generator標(biāo)記為coroutine類型,然后切诀,我們就把這個(gè)coroutine扔到EventLoop中執(zhí)行揩环。
 test()會(huì)首先打印出test_1,然后幅虑,yield from語(yǔ)法可以讓我們方便地調(diào)用另一個(gè)generator丰滑。由于asyncio.sleep()也是一個(gè)coroutine,所以線程不會(huì)等待asyncio.sleep()翘单,而是直接中斷并執(zhí)行下一個(gè)消息循環(huán)吨枉。當(dāng)asyncio.sleep()返回時(shí),線程就可以從yield from拿到返回值(此處是None)哄芜,然后接著執(zhí)行下一行語(yǔ)句貌亭。
 把a(bǔ)syncio.sleep(1)看成是一個(gè)耗時(shí)1秒的IO操作,在此期間认臊,主線程并未等待圃庭,而是去執(zhí)行EventLoop中其他可以執(zhí)行的coroutine了失晴,因此可以實(shí)現(xiàn)并發(fā)執(zhí)行剧腻。

asynico/await

為了簡(jiǎn)化并更好地標(biāo)識(shí)異步IO,從Python 3.5開始引入了新的語(yǔ)法async和await涂屁,可以讓coroutine的代碼更簡(jiǎn)潔易讀书在。
 請(qǐng)注意,async和await是針對(duì)coroutine的新語(yǔ)法拆又,要使用新的語(yǔ)法儒旬,只需要做兩步簡(jiǎn)單的替換:

  • 把@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é)果與之前一致栈源。
說(shuō)明:與前一節(jié)相比,這里只是把yield from換成了await竖般,@asyncio.coroutine換成了async甚垦,其余不變。

gevent

同python2.x用法一樣涣雕。

4艰亮、協(xié)程VS多線程

如果通過(guò)以上介紹,你已經(jīng)明白多線程與協(xié)程的不同之處胞谭,那么我想測(cè)試也就沒(méi)有必要了垃杖。因?yàn)楫?dāng)線程越來(lái)越多時(shí),多線程主要的開銷花費(fèi)在線程切換上丈屹,而協(xié)程是在一個(gè)線程內(nèi)切換的,因此開銷小很多,這也許就是兩者性能的根本差異之處吧旺垒。(個(gè)人觀點(diǎn))

異步爬蟲

也許關(guān)心協(xié)程的朋友彩库,大部分是用其寫爬蟲(因?yàn)閰f(xié)程能很好的解決IO阻塞問(wèn)題),然而我發(fā)現(xiàn)常用的urllib先蒋、requests無(wú)法與asyncio結(jié)合使用骇钦,可能是因?yàn)榕老x模塊本身是同步的(也可能是我沒(méi)找到用法)。那么對(duì)于異步爬蟲的需求竞漾,又該怎么使用協(xié)程呢眯搭?或者說(shuō)怎么編寫異步爬蟲?
給出幾個(gè)我所了解的方案:

  • grequests (requests模塊的異步化)
  • 爬蟲模塊+gevent(比較推薦這個(gè))
  • aiohttp (這個(gè)貌似資料不多业岁,目前我也不太會(huì)用)
  • asyncio內(nèi)置爬蟲功能 (這個(gè)也比較難用)

協(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 = [
    'http://www.google.com',
    # ... 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)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鳞仙,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子笔时,更是在濱河造成了極大的恐慌棍好,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件允耿,死亡現(xiàn)場(chǎng)離奇詭異借笙,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)较锡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門业稼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蚂蕴,你說(shuō)我怎么就攤上這事低散。” “怎么了掂墓?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵谦纱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我君编,道長(zhǎng)跨嘉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任吃嘿,我火速辦了婚禮祠乃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘兑燥。我一直安慰自己亮瓷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布降瞳。 她就那樣靜靜地躺著嘱支,像睡著了一般蚓胸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上除师,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天沛膳,我揣著相機(jī)與錄音,去河邊找鬼汛聚。 笑死锹安,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的倚舀。 我是一名探鬼主播叹哭,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼痕貌!你這毒婦竟也來(lái)了风罩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤芯侥,失蹤者是張志新(化名)和其女友劉穎泊交,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柱查,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡廓俭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了唉工。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片研乒。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖淋硝,靈堂內(nèi)的尸體忽然破棺而出雹熬,到底是詐尸還是另有隱情,我是刑警寧澤谣膳,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布竿报,位于F島的核電站,受9級(jí)特大地震影響继谚,放射性物質(zhì)發(fā)生泄漏烈菌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一花履、第九天 我趴在偏房一處隱蔽的房頂上張望芽世。 院中可真熱鬧,春花似錦诡壁、人聲如沸济瓢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)旺矾。三九已至蔑鹦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宠漩,已是汗流浹背举反。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工懊直, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扒吁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓室囊,卻偏偏與公主長(zhǎng)得像雕崩,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子融撞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

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