當前位置:
首頁 > 知識 > Python 協程

Python 協程

(點擊

上方藍字

,快速關注我們)




來源:nMask"s Blog



thief.one/2017/02/20/Python%E5%8D%8F%E7%A8%8B/


如有好文章投稿,請點擊 → 這裡了解詳情






真正有知識的人的成長過程,就像麥穗的成長過程:麥穗空的時候,麥子長得很快,麥穗驕傲地高高昂起,但是,麥穗成熟飽滿時,它們開始謙虛,垂下麥芒。


——蒙田《蒙田隨筆全集》




上篇《Python 多線程雞年不雞肋》論述了關於python多線程是否是雞肋的問題,得到了一些網友的認可,當然也有一些不同意見,表示協程比多線程不知強多少,在協程面前多線程算是雞肋。好吧,對此我也表示贊同,然而上篇我論述的觀點不在於多線程與協程的比較,而是在於IO密集型程序中,多線程尚有用武之地。




對於協程,我表示其效率確非多線程能比,但本人對此了解並不深入,因此最近幾日參考了一些資料,學習整理了一番,在此分享出來僅供大家參考,如有謬誤請指正,多謝。申明:本文介紹的協程是入門級別,大神請繞道而行,謹防入坑。




文章思路:本文將先介紹協程的概念,然後分別介紹Python2.x與3.x下協程的用法,最終將協程與多線程做比較並介紹非同步爬蟲模塊。



協程




概念




協程,又稱微線程,纖程,英文名Coroutine。協程的作用,是在執行函數A時,可以隨時中斷,去執行函數B,然後中斷繼續執行函數A(可以自由切換)。但這一過程並不是函數調用(沒有調用語句),這一整個過程看似像多線程,然而協程只有一個線程執行。




優勢






  • 執行效率極高,因為子程序切換(函數)不是線程切換,由程序自身控制,沒有切換線程的開銷。所以與多線程相比,線程的數量越多,協程性能的優勢越明顯。



  • 不需要多線程的鎖機制,因為只有一個線程,也不存在同時寫變數衝突,在控制共享資源時也不需要加鎖,因此執行效率高很多。




  說明:協程可以處理IO密集型程序的效率問題,但是處理CPU密集型不是它的長處,如要充分發揮CPU利用率可以結合多進程+協程。




以上只是協程的一些概念,可能聽起來比較抽象,那麼我結合代碼講一講吧。這裡主要介紹協程在Python的應用,Python2對協程的支持比較有限,生成器的yield實現了一部分但不完全,gevent模塊倒是有比較好的實現;Python3.4以後引入了asyncio模塊,可以很好的使用協程。




Python2.x協程




python2.x協程應用:






  • yield



  • gevent




python2.x中支持協程的模塊不多,gevent算是比較常用的,這裡就簡單介紹一下gevent的用法。




Gevent




gevent是第三方庫,通過greenlet實現協程,其基本思想:


當一個greenlet遇到IO操作時,比如訪問網路,就自動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行。由於IO操作非常耗時,經常使程序處於等待狀態,有了gevent為我們自動切換協程,就保證總有greenlet在運行,而不是等待IO。




Install





pip install gevent




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




Usage




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





#! -*- 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

)




運行結果:





start

0


start

1


start

2


end

2


end

0


end

1




說明:從結果上來看,執行get_body的順序應該先是輸出」start」,然後執行到urllib2時碰到IO堵塞,則會自動切換運行下一個程序(繼續執行get_body輸出start),直到urllib2返回結果,再執行end。也就是說,程序沒有等待urllib2請求網站返回結果,而是直接先跳過了,等待執行完畢再回來獲取返回值。值得一提的是,在此過程中,只有一個線程在執行,因此這與多線程的概念是不一樣的。




換成多線程的代碼看看:





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

()




運行結果:





start

0


start

1


start

2


end

1


end

2


end

0




說明:從結果來看,多線程與協程的效果一樣,都是達到了IO阻塞時切換的功能。不同的是,多線程切換的是線程(線程間切換),協程切換的是上下文(可以理解為執行的函數)。而切換線程的開銷明顯是要大於切換上下文的開銷,因此當線程越多,協程的效率就越比多線程的高。(猜想多進程的切換開銷應該是最大的)




Gevent使用說明






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



  • gevent.spawn 啟動協程,參數為函數名稱,參數名稱



  • gevent.joinall 停止協程




Python3.x協程




為了測試Python3.x下的協程應用,我在virtualenv下安裝了python3.6的環境。


python3.x協程應用:






  • asynico + yield from(python3.4)



  • asynico + await(python3.5)



  • gevent




Python3.4以後引入了asyncio模塊,可以很好的支持協程。




asynico




asyncio是Python 3.4版本引入的標準庫,直接內置了對非同步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

()




運行結果:





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




說明:從運行結果可以看到,跟gevent達到的效果一樣,也是在遇到IO操作時進行切換(所以先輸出test_1,等test_1輸出完再輸出test_2)。但此處我有一點不明,test_1的輸出為什麼不是按照順序執行的呢?可以對比gevent的輸出結果(希望大神能解答一下)。




asyncio說明




@asyncio.coroutine把一個generator標記為coroutine類型,然後,我們就把這個coroutine扔到EventLoop中執行。


test()會首先列印出test_1,然後,yield from語法可以讓我們方便地調用另一個generator。由於asyncio.sleep()也是一個coroutine,所以線程不會等待asyncio.sleep(),而是直接中斷並執行下一個消息循環。當asyncio.sleep()返回時,線程就可以從yield from拿到返回值(此處是None),然後接著執行下一行語句。


把asyncio.sleep(1)看成是一個耗時1秒的IO操作,在此期間,主線程並未等待,而是去執行EventLoop中其他可以執行的coroutine了,因此可以實現並發執行。




asynico/await




為了簡化並更好地標識非同步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

()




運行結果與之前一致。




說明:與前一節相比,這裡只是把yield from換成了await,@asyncio.coroutine換成了async,其餘不變。




gevent




同python2.x用法一樣。




協程VS多線程




如果通過以上介紹,你已經明白多線程與協程的不同之處,那麼我想測試也就沒有必要了。因為當線程越來越多時,多線程主要的開銷花費在線程切換上,而協程是在一個線程內切換的,因此開銷小很多,這也許就是兩者性能的根本差異之處吧。(個人觀點)




非同步爬蟲




也許關心協程的朋友,大部分是用其寫爬蟲(因為協程能很好的解決IO阻塞問題),然而我發現常用的urllib、requests無法與asyncio結合使用,可能是因為爬蟲模塊本身是同步的(也可能是我沒找到用法)。那麼對於非同步爬蟲的需求,又該怎麼使用協程呢?或者說怎麼編寫非同步爬蟲?






  • grequests (requests模塊的非同步化)



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



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



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




協程池




作用:控制協程數量





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

)




看完本文有收穫?請轉

發分享給更多人


關注「P

ython開發者」,提升Python技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python 的精彩文章:

5 個很好的 Python 面試題
數據專家必知必會的7款Python工具
Python超級程序員使用的開發工具
基於 Agent 的模型入門:Python 實現隔離模擬
編程語言中的超級英雄們:Python如同蝙蝠戰車,Ruby可比蝙蝠俠……

TAG:Python |

您可能感興趣

協程-以Python和Go為例
Python 標準庫之 collections 使用教程
Python之Sklearn使用教程
Python Web 應用程序 Tornado 框架簡介
Python非同步Web編程
Python入門篇-python安裝教程
Python鏈式操作:PyFunctional
Python之tworoutine
Python中使用Type hinting 和 annotations
Andrew Ng經典機器學習課程的Python實現2
使用 VS Code 進行 Python 編程
Python 繪圖庫 Matplotlib 入門教程
在 Kubernetes 上運行一個 Python 應用程序
python threading中處理主進程和子線程的關係
Python之Fpgrowth規則探尋
如何 Docker 化 Python Django 應用程序
Process-Forest-Window進程日誌分析工具;python版的BloodHound
Windows下Python用管理員執行命令或者程序
Python學習筆記-Python的安裝
Why Python 終