setInterval(函數(shù)名,1000); t: Timer = new Timer(1000, 5); t.addEventListener(TimerEvent.TIMER,函數(shù)名); t.addEventListener(TimerEvent.TIMER_COMPLETE, 函數(shù)名); t.start();
專注于為中小企業(yè)提供網(wǎng)站制作、成都做網(wǎng)站服務(wù),電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業(yè)馬山免費做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了上1000家企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實現(xiàn)規(guī)模擴充和轉(zhuǎn)變。
yield相當(dāng)于return,他將相應(yīng)的值返回給調(diào)用next()或者send()的調(diào)用者,從而交出了CPU使用權(quán),而當(dāng)調(diào)用者再次調(diào)用next()或者send()的時候,又會返回到y(tǒng)ield中斷的地方,如果send有參數(shù),還會將參數(shù)返回給yield賦值的變量,如果沒有就和next()一樣賦值為None。但是這里會遇到一個問題,就是嵌套使用generator時外層的generator需要寫大量代碼,看如下示例:?
注意以下代碼均在Python3.6上運行調(diào)試
#!/usr/bin/env python# encoding:utf-8def inner_generator():
i = 0
while True:
i = yield i ? ? ? ?if i 10: ? ? ? ? ? ?raise StopIterationdef outer_generator():
print("do something before yield")
from_inner = 0
from_outer = 1
g = inner_generator()
g.send(None) ? ?while 1: ? ? ? ?try:
from_inner = g.send(from_outer)
from_outer = yield from_inner ? ? ? ?except StopIteration: ? ? ? ? ? ?breakdef main():
g = outer_generator()
g.send(None)
i = 0
while 1: ? ? ? ?try:
i = g.send(i + 1)
print(i) ? ? ? ?except StopIteration: ? ? ? ? ? ?breakif __name__ == '__main__':
main()1234567891011121314151617181920212223242526272829303132333435363738394041
為了簡化,在Python3.3中引入了yield from
yield from
使用yield from有兩個好處,
1、可以將main中send的參數(shù)一直返回給最里層的generator,?
2、同時我們也不需要再使用while循環(huán)和send (), next()來進行迭代。
我們可以將上邊的代碼修改如下:
def inner_generator():
i = 0
while True:
i = yield i ? ? ? ?if i 10: ? ? ? ? ? ?raise StopIterationdef outer_generator():
print("do something before coroutine start") ? ?yield from inner_generator()def main():
g = outer_generator()
g.send(None)
i = 0
while 1: ? ? ? ?try:
i = g.send(i + 1)
print(i) ? ? ? ?except StopIteration: ? ? ? ? ? ?breakif __name__ == '__main__':
main()1234567891011121314151617181920212223242526
執(zhí)行結(jié)果如下:
do something before coroutine start123456789101234567891011
這里inner_generator()中執(zhí)行的代碼片段我們實際就可以認(rèn)為是協(xié)程,所以總的來說邏輯圖如下:?
接下來我們就看下究竟協(xié)程是啥樣子
協(xié)程coroutine
協(xié)程的概念應(yīng)該是從進程和線程演變而來的,他們都是獨立的執(zhí)行一段代碼,但是不同是線程比進程要輕量級,協(xié)程比線程還要輕量級。多線程在同一個進程中執(zhí)行,而協(xié)程通常也是在一個線程當(dāng)中執(zhí)行。它們的關(guān)系圖如下:
我們都知道Python由于GIL(Global Interpreter Lock)原因,其線程效率并不高,并且在*nix系統(tǒng)中,創(chuàng)建線程的開銷并不比進程小,因此在并發(fā)操作時,多線程的效率還是受到了很大制約的。所以后來人們發(fā)現(xiàn)通過yield來中斷代碼片段的執(zhí)行,同時交出了cpu的使用權(quán),于是協(xié)程的概念產(chǎn)生了。在Python3.4正式引入了協(xié)程的概念,代碼示例如下:
import asyncio# Borrowed from countdown(number, n):
while n 0:
print('T-minus', n, '({})'.format(number)) ? ? ? ?yield from asyncio.sleep(1)
n -= 1loop = asyncio.get_event_loop()
tasks = [
asyncio.ensure_future(countdown("A", 2)),
asyncio.ensure_future(countdown("B", 3))]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()12345678910111213141516
示例顯示了在Python3.4引入兩個重要概念協(xié)程和事件循環(huán),?
通過修飾符@asyncio.coroutine定義了一個協(xié)程,而通過event loop來執(zhí)行tasks中所有的協(xié)程任務(wù)。之后在Python3.5引入了新的async await語法,從而有了原生協(xié)程的概念。
async await
在Python3.5中,引入了ayncawait 語法結(jié)構(gòu),通過”aync def”可以定義一個協(xié)程代碼片段,作用類似于Python3.4中的@asyncio.coroutine修飾符,而await則相當(dāng)于”yield from”。
先來看一段代碼,這個是我剛開始使用asyncawait語法時,寫的一段小程序。
#!/usr/bin/env python# encoding:utf-8import asyncioimport requestsimport time
async def wait_download(url):
response = await requets.get(url)
print("get {} response complete.".format(url))
async def main():
start = time.time()
await asyncio.wait([
wait_download(""),
wait_download(""),
wait_download("")])
end = time.time()
print("Complete in {} seconds".format(end - start))
loop = asyncio.get_event_loop()
loop.run_until_complete(main())12345678910111213141516171819202122232425
這里會收到這樣的報錯:
Task exception was never retrieved
future: Task finished coro=wait_download() done, defined at asynctest.py:9 exception=TypeError("object Response can't be used in 'await' expression",)
Traceback (most recent call last):
File "asynctest.py", line 10, in wait_download
data = await requests.get(url)
TypeError: object Response can't be used in 'await' expression123456
這是由于requests.get()函數(shù)返回的Response對象不能用于await表達式,可是如果不能用于await,還怎么樣來實現(xiàn)異步呢??
原來Python的await表達式是類似于”yield from”的東西,但是await會去做參數(shù)檢查,它要求await表達式中的對象必須是awaitable的,那啥是awaitable呢? awaitable對象必須滿足如下條件中其中之一:
1、A native coroutine object returned from a native coroutine function .
原生協(xié)程對象
2、A generator-based coroutine object returned from a function decorated with types.coroutine() .
types.coroutine()修飾的基于生成器的協(xié)程對象,注意不是Python3.4中asyncio.coroutine
3、An object with an await method returning an iterator.
實現(xiàn)了await method,并在其中返回了iterator的對象
根據(jù)這些條件定義,我們可以修改代碼如下:
#!/usr/bin/env python# encoding:utf-8import asyncioimport requestsimport time
async def download(url): # 通過async def定義的函數(shù)是原生的協(xié)程對象
response = requests.get(url)
print(response.text)
async def wait_download(url):
await download(url) # 這里download(url)就是一個原生的協(xié)程對象
print("get {} data complete.".format(url))
async def main():
start = time.time()
await asyncio.wait([
wait_download(""),
wait_download(""),
wait_download("")])
end = time.time()
print("Complete in {} seconds".format(end - start))
loop = asyncio.get_event_loop()
loop.run_until_complete(main())123456789101112131415161718192021222324252627282930
好了現(xiàn)在一個真正的實現(xiàn)了異步編程的小程序終于誕生了。?
而目前更牛逼的異步是使用uvloop或者pyuv,這兩個最新的Python庫都是libuv實現(xiàn)的,可以提供更加高效的event loop。
uvloop和pyuv
pyuv實現(xiàn)了Python2.x和3.x,但是該項目在github上已經(jīng)許久沒有更新了,不知道是否還有人在維護。?
uvloop只實現(xiàn)了3.x, 但是該項目在github上始終活躍。
它們的使用也非常簡單,以uvloop為例,只需要添加以下代碼就可以了
import asyncioimport uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())123
如果你厭倦了多線程,不妨試試python的異步編程,再引入async, await關(guān)鍵字之后語法變得更加簡潔和直觀,又經(jīng)過幾年的生態(tài)發(fā)展,現(xiàn)在是一個很不錯的并發(fā)模型。
下面介紹一下python異步編程的方方面面。
因為GIL的存在,所以Python的多線程在CPU密集的任務(wù)下顯得無力,但是對于IO密集的任務(wù),多線程還是足以發(fā)揮多線程的優(yōu)勢的,而異步也是為了應(yīng)對IO密集的任務(wù),所以兩者是一個可以相互替代的方案,因為設(shè)計的不同,理論上異步要比多線程快,因為異步的花銷更少, 因為不需要額外系統(tǒng)申請額外的內(nèi)存,而線程的創(chuàng)建跟系統(tǒng)有關(guān),需要分配一定量的內(nèi)存,一般是幾兆,比如linux默認(rèn)是8MB。
雖然異步很好,比如可以使用更少的內(nèi)存,比如更好地控制并發(fā)(也許你并不這么認(rèn)為:))。但是由于async/await 語法的存在導(dǎo)致與之前的語法有些割裂,所以需要適配,需要付出額外的努力,再者就是生態(tài)遠(yuǎn)遠(yuǎn)沒有同步編程強大,比如很多庫還不支持異步,所以你需要一些額外的適配。
為了不給其他網(wǎng)站帶來困擾,這里首先在自己電腦啟動web服務(wù)用于測試,代碼很簡單。
本文所有依賴如下:
所有依賴可通過代碼倉庫的requirements.txt一次性安裝。
首先看一個錯誤的例子
輸出如下:
發(fā)現(xiàn)花費了3秒,不符合預(yù)期呀。。。。這是因為雖然用了協(xié)程,但是每個協(xié)程是串行的運行,也就是說后一個等前一個完成之后才開始,那么這樣的異步代碼并沒有并發(fā),所以我們需要讓這些協(xié)程并行起來
為了讓代碼變動的不是太多,所以這里用了一個笨辦法來等待所有任務(wù)完成, 之所以在main函數(shù)中等待是為了不讓ClientSession關(guān)閉, 如果你移除了main函數(shù)中的等待代碼會發(fā)現(xiàn)報告異常 RuntimeError: Session is closed ,而代碼里的解決方案非常的不優(yōu)雅,需要手動的等待,為了解決這個問題,我們再次改進代碼。
這里解決的方式是通過 asyncio.wait 方法等待一個協(xié)程列表,默認(rèn)是等待所有協(xié)程結(jié)束后返回,會返回一個完成(done)列表,以及一個待辦(pending)列表。
如果我們不想要協(xié)程對象而是結(jié)果,那么我們可以使用 asyncio.gather
結(jié)果輸出如下:
通過 asyncio.ensure_future 我們就能創(chuàng)建一個協(xié)程,跟調(diào)用一個函數(shù)差別不大,為了等待所有任務(wù)完成之后退出,我們需要使用 asyncio.wait 等方法來等待,如果只想要協(xié)程輸出的結(jié)果,我們可以使用 asyncio.gather 來獲取結(jié)果。
雖然前面能夠隨心所欲的創(chuàng)建協(xié)程,但是就像多線程一樣,我們也需要處理協(xié)程之間的同步問題,為了保持語法及使用情況的一致,多線程中用到的同步功能,asyncio中基本也能找到, 并且用法基本一致,不一致的地方主要是需要用異步的關(guān)鍵字,比如 async with/ await 等
通過鎖讓并發(fā)慢下來,讓協(xié)程一個一個的運行。
輸出如下:
通過觀察很容易發(fā)現(xiàn),并發(fā)的速度因為鎖而慢下來了,因為每次只有一個協(xié)程能獲得鎖,所以并發(fā)變成了串行。
通過事件來通知特定的協(xié)程開始工作,假設(shè)有一個任務(wù)是根據(jù)http響應(yīng)結(jié)果選擇是否激活。
輸出如下:
可以看到事件(Event)等待者都是在得到響應(yīng)內(nèi)容之后輸出,并且事件(Event)可以是多個協(xié)程同時等待。
上面的事件雖然很棒,能夠在不同的協(xié)程之間同步狀態(tài),并且也能夠一次性同步所有的等待協(xié)程,但是還不夠精細(xì)化,比如想通知指定數(shù)量的等待協(xié)程,這個時候Event就無能為力了,所以同步原語中出現(xiàn)了Condition。
輸出如下:
可以看到,前面兩個等待的協(xié)程是在同一時刻完成,而不是全部等待完成。
通過創(chuàng)建協(xié)程的數(shù)量來控制并發(fā)并不是非常優(yōu)雅的方式,所以可以通過信號量的方式來控制并發(fā)。
輸出如下:
可以發(fā)現(xiàn),雖然同時創(chuàng)建了三個協(xié)程,但是同一時刻只有兩個協(xié)程工作,而另外一個協(xié)程需要等待一個協(xié)程讓出信號量才能運行。
無論是協(xié)程還是線程,任務(wù)之間的狀態(tài)同步還是很重要的,所以有了應(yīng)對各種同步機制的同步原語,因為要保證一個資源同一個時刻只能一個任務(wù)訪問,所以引入了鎖,又因為需要一個任務(wù)等待另一個任務(wù),或者多個任務(wù)等待某個任務(wù),因此引入了事件(Event),但是為了更精細(xì)的控制通知的程度,所以又引入了條件(Condition), 通過條件可以控制一次通知多少的任務(wù)。
有時候的并發(fā)需求是通過一個變量控制并發(fā)任務(wù)的并發(fā)數(shù)而不是通過創(chuàng)建協(xié)程的數(shù)量來控制并發(fā),所以引入了信號量(Semaphore),這樣就可以在創(chuàng)建的協(xié)程數(shù)遠(yuǎn)遠(yuǎn)大于并發(fā)數(shù)的情況下讓協(xié)程在指定的并發(fā)量情況下并發(fā)。
不得不承認(rèn)異步編程相比起同步編程的生態(tài)要小的很多,所以不可能完全異步編程,因此需要一種方式兼容。
多線程是為了兼容同步得代碼。
多進程是為了利用CPU多核的能力。
輸出如下:
可以看到總耗時1秒,說明所有的線程跟進程是同時運行的。
下面是本人使用過的一些異步庫,僅供參考
web框架
http客戶端
數(shù)據(jù)庫
ORM
雖然異步庫發(fā)展得還算不錯,但是中肯的說并沒有覆蓋方方面面。
雖然我鼓勵大家嘗試異步編程,但是本文的最后卻是讓大家謹(jǐn)慎的選擇開發(fā)環(huán)境,如果你覺得本文的并發(fā),同步,兼容多線程,多進程不值得一提,那么我十分推薦你嘗試以異步編程的方式開始一個新的項目,如果你對其中一些還有疑問或者你確定了要使用的依賴庫并且大多數(shù)是沒有異步庫替代的,那么我還是建議你直接按照自己擅長的同步編程開始。
異步編程雖然很不錯,不過,也許你并不需要。