當前位置:
首頁 > 知識 > Python 中的非同步編程:Asyncio

Python 中的非同步編程:Asyncio

(點擊

上方藍字

,快速關注我們)




編譯:開源中國 



www.oschina.net/translate/asynchronous-programming-in-python-asyncio


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




如果你已經決定要理解 Python 的非同步部分,歡迎來到我們的「Asyncio How-to 」。



註:哪怕連異動範式的存在都不知道的情況下,你也可以成功地使用 Python。但是,如果你對底層運行模式感興趣的話,asyncio 絕對值得查看。




非同步是怎麼一回事?




在傳統的順序編程中, 所有發送給解釋器的指令會一條條被執行。此類代碼的輸出容易顯現和預測。 但是…




譬如說你有一個腳本向3個不同伺服器請求數據。 有時,誰知什麼原因,發送給其中一個伺服器的請求可能意外地執行了很長時間。想像一下從第二個伺服器獲取數據用了10秒鐘。在你等待的時候,整個腳本實際上什麼也沒幹。如果你可以寫一個腳本可以不去等待第二個請求而是僅僅跳過它,然後開始執行第三個請求,然後回到第二個請求,執行之前離開的位置會怎麼樣呢。就是這樣。你通過切換任務最小化了空轉時間。儘管如此,當你需要一個幾乎沒有I/O的簡單腳本時,你不想用非同步代碼。




還有一件重要的事情要提,所有代碼在一個線程中運行。所以如果你想讓程序的一部分在後台執行同時干一些其他事情,那是不可能的。



準備開始




這是 asyncio 主概念最基本的定義:






  • 協程

    — 消費數據的生成器,但是不生成數據。Python 2.5 介紹了一種新的語法讓發送數據到生成器成為可能。我推薦查閱David Beazley 「A Curious Course on Coroutines and Concurrency」 關於協程的詳細介紹。



  • 任務

    — 協程調度器。如果你觀察下面的代碼,你會發現它只是讓 event_loop 儘快調用它的_step ,同時 _step 只是調用協程的下一步。





class

 

Task

(

futures

.

Future

)

:

  


    

def

 

__init__

(

self

,

 

coro

,

 

loop

=

None

)

:


        

super

().

__init__

(

loop

=

loop

)


        

...


        

self

.

_loop

.

call_soon

(

self

.

_step

)

 


    

def

 

_step

(

self

)

:


            

...


        

try

:


            

...


            

result

 

=

 

next

(

self

.

_coro

)


        

except

 

StopIteration

 

as

 

exc

:


            

self

.

set_result

(

exc

.

value

)


        

except

 

BaseException

 

as

 

exc

:


            

self

.

set_exception

(

exc

)


            

raise


        

else

:


            

...


            

self

.

_loop

.

call_soon

(

self

.

_step

)






  • 事件循環

    — 把它想成 asyncio 的中心執行器。




現在我們看一下所有這些如何融為一體。正如我之前提到的,非同步代碼在一個線程中運行。





從上圖可知:




1.消息循環是在線程中執行




2.從隊列中取得任務




3.每個任務在協程中執行下一步動作




4.如果在一個協程中調用另一個協程(await <coroutine_name>),會觸發上下文切換,掛起當前協程,並保存現場環境(變數,狀態),然後載入被調用協程




5.如果協程的執行到阻塞部分(阻塞I/O,Sleep),當前協程會掛起,並將控制權返回到線程的消息循環中,然後消息循環繼續從隊列中執行下一個任務...以此類推




6.隊列中的所有任務執行完畢後,消息循環返回第一個任務




非同步和同步的代碼對比




現在我們實際驗證非同步模式的切實有效,我會比較兩段 python 腳本,這兩個腳本除了sleep 方法外,其餘部分完全相同。在第一個腳本里,我會用標準的 time.sleep 方法,在第二個腳本里使用 asyncio.sleep 的非同步方法。




這裡使用 Sleep 是因為它是一個用來展示非同步方法如何操作 I/O 的最簡單辦法。




使用同步 sleep 方法的代碼:





import

 

asyncio

  


import

 

time

  


from

 

datetime

 

import

 

datetime


 


 


async

 

def

 

custom_sleep

()

:

  


    

print

(

"SLEEP"

,

 

datetime

.

now

())


    

time

.

sleep

(

1

)


 


async

 

def

 

factorial

(

name

,

 

number

)

:

  


    

f

 

=

 

1


    

for

 

i

 

in

 

range

(

2

,

 

number

+

1

)

:


        

print

(

"Task {}: Compute factorial({})"

.

format

(

name

,

 

i

))


        

await

 

custom_sleep

()


        

f

 

*=

 

i


    

print

(

"Task {}: factorial({}) is {}
"

.

format

(

name

,

 

number

,

 

f

))


 


 


start

 

=

 

time

.

time

()

  


loop

 

=

 

asyncio

.

get_event_loop

()


 


tasks

 

=

 

[

  


    

asyncio

.

ensure_future

(

factorial

(

"A"

,

 

3

)),


    

asyncio

.

ensure_future

(

factorial

(

"B"

,

 

4

)),


]


loop

.

run_until_complete

(

asyncio

.

wait

(

tasks

))

  


loop

.

close

()


 


end

 

=

 

time

.

time

()

  


print

(

"Total time: {}"

.

format

(

end

 

-

 

start

))




腳本輸出:





Task

 

A

:

 

Compute

 

factorial

(

2

)

  


SLEEP

 

2017

-

04

-

06

 

13

:

39

:

56.207479

  


Task

 

A

:

 

Compute

 

factorial

(

3

)

  


SLEEP

 

2017

-

04

-

06

 

13

:

39

:

57.210128

  


Task

 

A

:

 

factorial

(

3

)

 

is

 

6


 


Task

 

B

:

 

Compute

 

factorial

(

2

)

  


SLEEP

 

2017

-

04

-

06

 

13

:

39

:

58.210778

  


Task

 

B

:

 

Compute

 

factorial

(

3

)

  


SLEEP

 

2017

-

04

-

06

 

13

:

39

:

59.212510

  


Task

 

B

:

 

Compute

 

factorial

(

4

)

  


SLEEP

 

2017

-

04

-

06

 

13

:

40

:

00.217308

  


Task

 

B

:

 

factorial

(

4

)

 

is

 

24


 


Total

 

time

:

 

5.016386032104492




使用非同步 Sleep  的代碼:





import

 

asyncio

  


import

 

time

  


from

 

datetime

 

import

 

datetime


 


 


async

 

def

 

custom_sleep

()

:

  


    

print

(

"SLEEP {}
"

.

format

(

datetime

.

now

()))


    

await

 

asyncio

.

sleep

(

1

)


 


async

 

def

 

factorial

(

name

,

 

number

)

:

  


    

f

 

=

 

1


    

for

 

i

 

in

 

range

(

2

,

 

number

+

1

)

:


        

print

(

"Task {}: Compute factorial({})"

.

format

(

name

,

 

i

))


        

await

 

custom_sleep

()


        

f

 

*=

 

i


    

print

(

"Task {}: factorial({}) is {}
"

.

format

(

name

,

 

number

,

 

f

))


 


 


start

 

=

 

time

.

time

()

  


loop

 

=

 

asyncio

.

get_event_loop

()


 


tasks

 

=

 

[

  


    

asyncio

.

ensure_future

(

factorial

(

"A"

,

 

3

)),


    

asyncio

.

ensure_future

(

factorial

(

"B"

,

 

4

)),


]


loop

.

run_until_complete

(

asyncio

.

wait

(

tasks

))

  


loop

.

close

()


 


end

 

=

 

time

.

time

()

  


print

(

"Total time: {}"

.

format

(

end

 

-

 

start

))




腳本輸出:





Task

 

A

:

 

Compute

 

factorial

(

2

)

  


SLEEP

 

2017

-

04

-

06

 

13

:

44

:

40.648665


 


Task

 

B

:

 

Compute

 

factorial

(

2

)

  


SLEEP

 

2017

-

04

-

06

 

13

:

44

:

40.648859


 


Task

 

A

:

 

Compute

 

factorial

(

3

)

  


SLEEP

 

2017

-

04

-

06

 

13

:

44

:

41.649564


 


Task

 

B

:

 

Compute

 

factorial

(

3

)

  


SLEEP

 

2017

-

04

-

06

 

13

:

44

:

41.649943


 


Task

 

A

:

 

factorial

(

3

)

 

is

 

6


 


Task

 

B

:

 

Compute

 

factorial

(

4

)

  


SLEEP

 

2017

-

04

-

06

 

13

:

44

:

42.651755


 


Task

 

B

:

 

factorial

(

4

)

 

is

 

24


 


Total

 

time

:

 

3.008226156234741




從輸出可以看到,非同步模式的代碼執行速度快了大概兩秒。當使用非同步模式的時候(每次調用  await asyncio.sleep(1) ),進程式控制制權會返回到主程序的消息循環里,並開始運行隊列的其他任務(任務A或者任務B)。




當使用標準的 sleep方法時,當前線程會掛起等待。什麼也不會做。實際上,標準的 sleep 過程中,當前線程也會返回一個 python 的解釋器,可以操作現有的其他線程,但這是另一個話題了。




推薦使用非同步模式編程的幾個理由




很多公司的產品都廣泛的使用了非同步模式,如 Facebook 旗下著名的 React Native 和 RocksDB 。像 Twitter 每天可以承載 50 億的用戶訪問,靠的也是非同步模式編程。所以說,通過代碼重構,或者改變模式方法,就能讓系統工作的更快,為什麼不去試一下呢?




看完本文有收穫?請轉

發分享給更多人


關注「P

ython開發者」,提升Python技能


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

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


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

機器學習演算法實踐:Logistic 回歸與梯度上升演算法
愛學習的程序員都關注了這些
你們先開發出來,我再提需求
Python 開發者節省時間的 10 個方法
27 個機器學習、數學、Python 速查表

TAG:Python開發者 |

您可能感興趣

Python非同步Web編程
Python鏈式操作:PyFunctional
Python中使用Type hinting 和 annotations
基於Asyncio的Python微框架:Quart
在Python中使用Elasticsearch
Python中的IO編程
Bayesian Personalized Ranking 演算法解析及Python實現
Ubuntu下系統在帶Python和conda中安裝的Python共存問題
Process-Forest-Window進程日誌分析工具;python版的BloodHound
Python 標準庫之 collections 使用教程
Python之tworoutine
Map和Reduce在Hadoop與Python中有何異同?
為什麼Python如此火?Why Python is so popular?
Python async/await 介紹
Python爬蟲學習Scrapy之Spiders
Numba和Cython如何加速Python代碼
如何 Docker 化 Python Django 應用程序
用Python彈鋼琴:mingus
Python 中最全面的 Socket 編程指南
Python 的 ChatOps 庫:Opsdroid 和 Errbot