Python 協(xié)程與 JavaScript 協(xié)程的對(duì)比


前言
共同訴求
隨著 cpu 多核化,都需要實(shí)現(xiàn)由于自身歷史原因(單線程環(huán)境)下的并發(fā)功能
簡(jiǎn)化代碼,避免回調(diào)地獄,關(guān)鍵字支持
有效利用操作系統(tǒng)資源和硬件:協(xié)程相比線程,占用資源更少,上下文更快
什么是協(xié)程?
可以暫停執(zhí)行(暫停的表達(dá)式稱為暫停點(diǎn)) 可以從掛起點(diǎn)恢復(fù)(保留其原始參數(shù)和局部變量) 事件循環(huán)是異步編程的底層基石
混亂的歷史
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)正" (之后的官方文檔就清晰了很多)
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
"""
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é)果
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
*/
Python 協(xié)程成熟體
協(xié)程(coroutine)
協(xié)程函數(shù):定義形式為 async def 的函數(shù);
協(xié)程對(duì)象:調(diào)用 協(xié)程函數(shù) 所返回的對(duì)象
舊式基于 generator(生成器)的協(xié)程
任務(wù)(Task 對(duì)象):
任務(wù) 被用來(lái)“并行的”調(diào)度協(xié)程, 當(dāng)一個(gè)協(xié)程通過(guò) asyncio.create_task() 等函數(shù)被封裝為一個(gè) 任務(wù),該協(xié)程會(huì)被自動(dòng)調(diào)度執(zhí)行
Task 對(duì)象被用來(lái)在事件循環(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。
未來(lái)對(duì)象(Future):
Future 對(duì)象用來(lái)鏈接 底層回調(diào)式代碼 和高層異步/等待式代碼。
不用回調(diào)方法編寫(xiě)異步代碼后,為了獲取異步調(diào)用的結(jié)果,引入一個(gè) Future 未來(lái)對(duì)象。Future 封裝了與 loop 的交互行為,add_done_callback 方法向 epoll 注冊(cè)回調(diào)函數(shù),當(dāng) result 屬性得到返回值后,會(huì)運(yùn)行之前注冊(cè)的回調(diào)函數(shù),向上傳遞給 coroutine。
幾種事件循環(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 看起來(lái)已經(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())
JavaScript 協(xié)程成熟體
Promise 繼續(xù)使用
pending: 初始狀態(tài),既不是成功,也不是失敗狀態(tài)。 fulfilled: 意味著操作成功完成。 rejected: 意味著操作失敗。
async、await語(yǔ)法糖
js 異步執(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í)行棧并開(kāi)始執(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();
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, MutationObserver等,本質(zhì)是直接在 Javascript 引擎中的執(zhí)行的沒(méi)有參與事件循環(huán)的任務(wù)
總結(jié)與對(duì)比
| 說(shuō)明 | python | JavaScript | 點(diǎn)評(píng) |
|---|---|---|---|
| 進(jìn)程 | 單進(jìn)程 | 單進(jìn)程 | 一致 |
| 中斷/恢復(fù) | yield,yield from,next,send | yield,next | 基本相同,但 JavaScript 對(duì) send 沒(méi)啥需求 |
| 未來(lái)對(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)境基本是黑盒,外部基本無(wú)法控制,對(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í) |
到這里就基本結(jié)束了,看完不知道你會(huì)有什么感想,如有錯(cuò)誤還請(qǐng)不吝賜教。
原文鏈接:https://www.cnblogs.com/lgjbky/p/14759463.html
文章轉(zhuǎn)載:Python編程學(xué)習(xí)圈
(版權(quán)歸原作者所有,侵刪)
![]()

點(diǎn)擊下方“閱讀原文”查看更多
