Python 協(xié)程與 JavaScript 協(xié)程的對(duì)比
1、
- 隨著 cpu 多核化,都需要實(shí)現(xiàn)由于自身歷史原因(單線程環(huán)境)下的并發(fā)功能
- 簡(jiǎn)化代碼,避免回調(diào)地獄,關(guān)鍵字支持
- 有效利用操作系統(tǒng)資源和硬件:協(xié)程相比線程,占用資源更少,上下文更快
2、什么是協(xié)程?
總結(jié)一句話,協(xié)程就是滿足下面條件的函數(shù):
- 可以暫停執(zhí)行(暫停的表達(dá)式稱為暫停點(diǎn))
- 可以從掛起點(diǎn)恢復(fù)(保留其原始參數(shù)和局部變量)
- 事件循環(huán)是異步編程的底層基石
3、混亂的歷史
3.1 Python 協(xié)程的進(jìn)化
- Python2.2 中,第一次引入了生成器
- Python2.5 中,yield 關(guān)鍵字被加入到語(yǔ)法中
- Python3.4 時(shí)有了
yield from
(yield from
約等于yield
+ 異常處理 +send
), 并試驗(yàn)性引入的異步 I/O 框架asyncio
(PEP 3156) - Python3.5 中新增了
async/await
語(yǔ)法(PEP 492) - Python3.6 中
asyncio
庫(kù)"轉(zhuǎn)正" (之后的官方文檔就清晰了很多)
在主線發(fā)展過程中,也出現(xiàn)了很多支線的協(xié)程實(shí)現(xiàn)如 Gevent
。
def foo(): print("foo start") a = yield 1 print("foo a", a) yield 2 yield 3 print("foo end") gen = foo() # print(gen.next()) # gen.send("a") # print(gen.next()) # print(foo().next()) # print(foo().next()) # 在python3.x版本中,python2.x的g.next()函數(shù)已經(jīng)更名為g.__next__(),使用next(g)也能達(dá)到相同效果。 # next()跟send()不同的地方是,next()只能以None作為參數(shù)傳遞,而send()可以傳遞yield的值. print(next(gen)) print(gen.send("a")) print(next(gen)) print(next(foo())) print(next(foo())) list(foo()) """ foo start 1 foo a a 2 3 foo start 1 foo start 1 foo start foo a None foo end """
4、JavaScript 協(xié)程的進(jìn)化
- 同步代碼
- 異步 JavaScript:
callback hell
- ES6 引入
Promise/a+,
生成器Generators
(語(yǔ)法function foo(){}*
可以賦予函數(shù)執(zhí)行暫停/保存上下文/恢復(fù)執(zhí)行狀態(tài)的功能), 新關(guān)鍵詞 yield 使生成器函數(shù)暫停。 - ES7 引入 async函數(shù)/await語(yǔ)法糖,async 可以聲明一個(gè)異步函數(shù)(將
Generator
函數(shù)和自動(dòng)執(zhí)行器,包裝在一個(gè)函數(shù)里),此函數(shù)需要返回一個(gè)Promise
對(duì)象。await
可以等待一個(gè)Promise
對(duì)象resolve
,并拿到結(jié)果
Promise
中也利用了回調(diào)函數(shù)。在 then
和 catch
方法中都傳入了一個(gè)回調(diào)函數(shù),分別在 Promise
被滿足和被拒絕時(shí)執(zhí)行,這樣就就能讓它能夠被鏈接起來完成一系列任務(wù)。
總之就是把層層嵌套的 callback
變成 .then().then()...,
從而使代碼編寫和閱讀更直觀。
生成器 Generator
的底層實(shí)現(xiàn)機(jī)制是協(xié)程 Coroutine
。
function* foo() { console.log("foo start") a = yield 1; console.log("foo a", a) yield 2; yield 3; console.log("foo end") } const gen = foo(); console.log(gen.next().value); // 1 // gen.send("a") // http://www.voidcn.com/article/p-syzbwqht-bvv.html SpiderMonkey引擎支持 send 語(yǔ)法 console.log(gen.next().value); // 2 console.log(gen.next().value); // 3 console.log(foo().next().value); // 1 console.log(foo().next().value); // 1 /* foo start 1 foo a undefined 2 3 foo start 1 foo start 1 */
5、Python 協(xié)程成熟體
可等待對(duì)象可以在 await
語(yǔ)句中使用,可等待對(duì)象有三種主要類型:協(xié)程(coroutine
), 任務(wù)(task
) 和 Future
。
5.1 協(xié)程(coroutine)
- 協(xié)程函數(shù):定義形式為
async def
的函數(shù); - 協(xié)程對(duì)象:調(diào)用 協(xié)程函數(shù) 所返回的對(duì)象
- 舊式基于
generator
(生成器)的協(xié)程
5.2 任務(wù)(Task 對(duì)象)
- 任務(wù) 被用來“并行的”調(diào)度協(xié)程, 當(dāng)一個(gè)協(xié)程通過
asyncio.create_task()
等函數(shù)被封裝為一個(gè) 任務(wù),該協(xié)程會(huì)被自動(dòng)調(diào)度執(zhí)行 - Task 對(duì)象被用來在事件循環(huán)中運(yùn)行協(xié)程。如果一個(gè)協(xié)程在等待一個(gè)
Future
對(duì)象,Task 對(duì)象會(huì)掛起該協(xié)程的執(zhí)行并等待該 Future 對(duì)象完成。當(dāng)該Future
對(duì)象 完成,被打包的協(xié)程將恢復(fù)執(zhí)行。 - 事件循環(huán)使用協(xié)同日程調(diào)度: 一個(gè)事件循環(huán)每次運(yùn)行一個(gè) Task 對(duì)象。而一個(gè) Task 對(duì)象會(huì)等待一個(gè) Future 對(duì)象完成,該事件循環(huán)會(huì)運(yùn)行其他 Task、回調(diào)或執(zhí)行 IO 操作。
asyncio.Task
從Future
繼承了其除Future.set_result()
和Future.set_exception()
以外的所有 API。
5.3 未來對(duì)象(Future)
Future
對(duì)象用來鏈接 底層回調(diào)式代碼 和高層異步/等待式代碼。
不用回調(diào)方法編寫異步代碼后,為了獲取異步調(diào)用的結(jié)果,引入一個(gè) Future
未來對(duì)象。Future
封裝了與 loop
的交互行為,add_done_callback
方法向 epoll 注冊(cè)回調(diào)函數(shù),當(dāng) result
屬性得到返回值后,會(huì)運(yùn)行之前注冊(cè)的回調(diào)函數(shù),向上傳遞給 coroutine
。
5.4幾種事件循環(huán)(event loop)
libevent/libev:
Gevent(greenlet + 前期 libevent,后期 libev)使用的網(wǎng)絡(luò)庫(kù),廣泛應(yīng)用;tornado:
tornado 框架自己實(shí)現(xiàn)的 IOLOOP;picoev:
meinheld(greenlet+picoev
)使用的網(wǎng)絡(luò)庫(kù),小巧輕量,相較于 libevent 在數(shù)據(jù)結(jié)構(gòu)和事件檢測(cè)模型上做了改進(jìn),所以速度更快。但從 github 看起來已經(jīng)年久失修,用的人不多。uvloop:
Python3 時(shí)代的新起之秀。Guido 操刀打造了 asyncio 庫(kù),asyncio
可以配置可插拔的event loop
,但需要滿足相關(guān)的 API 要求,uvloop 繼承自 libuv,將一些低層的結(jié)構(gòu)體和函數(shù)用 Python 對(duì)象包裝。目前 Sanic 框架基于這個(gè)庫(kù)
例子:
import asyncio import time async def exec(): await asyncio.sleep(2) print('exec') # 這種會(huì)和同步效果一直 # async def go(): # print(time.time()) # c1 = exec() # c2 = exec() # print(c1, c2) # await c1 # await c2 # print(time.time()) # 正確用法 async def go(): print(time.time()) await asyncio.gather(exec(),exec()) # 加入?yún)f(xié)程組統(tǒng)一調(diào)度 print(time.time()) if __name__ == "__main__": asyncio.run(go())
6、JavaScript 協(xié)程成熟體
6.1Promise 繼續(xù)使用
Promise
本質(zhì)是一個(gè)狀態(tài)機(jī),用于表示一個(gè)異步操作的最終完成 (或失敗), 及其結(jié)果值。它有三個(gè)狀態(tài):
pending:
初始狀態(tài),既不是成功,也不是失敗狀態(tài)。fulfilled:
意味著操作成功完成。rejected
: 意味著操作失敗。
最終 Promise
會(huì)有兩種狀態(tài),一種成功,一種失敗,當(dāng) pending
變化的時(shí)候,Promise 對(duì)象會(huì)根據(jù)最終的狀態(tài)調(diào)用不同的處理函數(shù)。
6.2 async、await語(yǔ)法糖
async
、await
是對(duì) Generator
和 Promise
組合的封裝,使原先的異步代碼在形式上更接近同步代碼的寫法,并且對(duì)錯(cuò)誤處理/條件分支/異常堆棧/調(diào)試等操作更友好。
6.3js 異步執(zhí)行的運(yùn)行機(jī)制
- 所有任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧。
- 主線程之外,還存在一個(gè)"任務(wù)隊(duì)列"(
task queue
)。只要異步任務(wù)有了運(yùn)行結(jié)果,就在"任務(wù)隊(duì)列"之中放置一個(gè)事件。 - 一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)讀取"任務(wù)隊(duì)列"。那些對(duì)應(yīng)的異步任務(wù),結(jié)束等待狀態(tài),進(jìn)入執(zhí)行棧并開始執(zhí)行。
遇到同步任務(wù)直接執(zhí)行,遇到異步任務(wù)分類為宏任務(wù)(macro-task
)和微任務(wù)(micro-task
)。
當(dāng)前執(zhí)行棧執(zhí)行完畢時(shí)會(huì)立刻先處理所有微任務(wù)隊(duì)列中的事件,然后再去宏任務(wù)隊(duì)列中取出一個(gè)事件。同一次事件循環(huán)中,微任務(wù)永遠(yuǎn)在宏任務(wù)之前執(zhí)行。
例子:
var sleep = function (time) { console.log("sleep start") return new Promise(function (resolve, reject) { setTimeout(function () { resolve(); }, time); }); }; async function exec() { await sleep(2000); console.log("sleep end") } async function go() { console.log(Date.now()) c1 = exec() console.log("-------1") c2 = exec() console.log(c1, c2) await c1; console.log("-------2") await c2; console.log(c1, c2) console.log(Date.now()) } go();
6.4 event loop 將任務(wù)劃分
主線程循環(huán)從"任務(wù)隊(duì)列"中讀取事件
宏隊(duì)列(macro task
)js 同步執(zhí)行的代碼塊,setTimeout
、setInterval
、XMLHttprequest
、setImmediate
、I/O、UI rendering
等,本質(zhì)是參與了事件循環(huán)的任務(wù)
微隊(duì)列(micro task)Promise
、process.nextTick
(node環(huán)境)、Object.observe
, MutationObserve
r等,本質(zhì)是直接在 Javascript 引擎中的執(zhí)行的沒有參與事件循環(huán)的任務(wù)
擴(kuò)展閱讀 Node.js 中的 EventLoop (Javascript運(yùn)營(yíng)機(jī)制詳解再淺談 Event Loop)
7、總結(jié)與對(duì)比
說明 | python | JavaScript | 點(diǎn)評(píng) |
---|---|---|---|
進(jìn)程 | 單進(jìn)程 | 單進(jìn)程 | 一致 |
中斷/恢復(fù) | yield,yield from,next,send | yield,next | 基本相同,但 JavaScript 對(duì) send 沒啥需求 |
未來對(duì)象(回調(diào)包裝) | Futures | Promise | 解決 callback,思路相同 |
生成器 | generator | Generator | 將 yield 封裝為協(xié)程Coroutine,思路一樣 |
成熟后關(guān)鍵詞 | async、await | async、await | 關(guān)鍵詞支持,一毛一樣 |
事件循環(huán) | asyncio 應(yīng)用的核心。事件循環(huán)會(huì)運(yùn)行異步任務(wù)和回調(diào),執(zhí)行網(wǎng)絡(luò) IO 操作,以及運(yùn)行子進(jìn)程。asyncio 庫(kù)支持的 API 較多,可控性高 | 基于瀏覽器環(huán)境基本是黑盒,外部基本無法控制,對(duì)任務(wù)有做優(yōu)先級(jí)分類,調(diào)度方式有區(qū)別 | 這里有很大區(qū)別,運(yùn)行環(huán)境不同,對(duì)任務(wù)的調(diào)度先后不同,Python 可能和 Node.js 關(guān)于事件循環(huán)的可比性更高些,這里還需需要繼續(xù)學(xué)習(xí) |
到此這篇關(guān)于Python 協(xié)程與 JavaScript 協(xié)程的對(duì)比的文章就介紹到這了,更多相關(guān)Python 協(xié)程與 JavaScript 協(xié)程內(nèi)容請(qǐng)搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!
版權(quán)聲明:本站文章來源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來,僅供學(xué)習(xí)參考,不代表本站立場(chǎng),如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。