最近在編程總是會遇到async, Mutex等字眼,今天來盤點以下同步和異步,順便復習以下作業系統
行程(Process)
正在運行的程式,人們寫的程式只定義了入口函式(比如__main_, main(), __main__),如果執行這個程式,作業系統會為這個程式生成行程,比如在linux中,內核會從0號行程fork()出一個新的行程,接著把我們的代碼裝載進這個行程,最后這個行程從入口函式開始一步步運行,行程具有一定的資源,比如虛擬記憶體空間,正在執行程式檔案,行程具有一定的標志,比如權限,調度優先級,行程運行狀態,
執行緒(Thread)
考慮到行程的創建和銷毀需要維護大量的資料結構(行程表),再加上隨著科技的發展,多核cpu逐漸稱為主流,為了充分利用多核,人們創造了更小的單位:執行緒,多個執行緒能夠共享同一個行程的資源,貌似linux在某個版本之后的調度演算法將cpu的最小調度單元改成了執行緒,從而增加了對多核的支持,
協程(Coroutine)
比執行緒更小的單位,協程的調度并不需要系統呼叫,而是在一個執行緒內部實作,相當于一個用戶態的的作業系統,但是基本的io操作依賴于執行緒的io系統呼叫,主要用來解決io密集執行緒過多的問題,比如1億個執行緒的io讀寫,那么有很多的時間耗費在了執行緒切換之上(執行緒也有資料結構,只是比行程簡單, 而且執行緒的切換是系統呼叫),因此人們干脆把調度演算法寫在執行緒中,在執行緒調度協程,那么就只有一個執行緒,1億個協程,時間雖然都花在了協程的切換之上,但由于協程的資料結構更簡單,且不用頻繁系統呼叫,耗時也減少了,
互斥
資源有限而需求無限,A和B都想買車票,一個售票口只能排隊,假如A在B前面,B只能進入等待,
同步(sync)
B在A之后才能執行,
- 上面互斥的例子,B只能等待A完成也是一種同步,
- 生產者和消費者和商店,消費者到商店購買產品,沒有產品只能等生產者造出產品送到商店才行,同步能讓消費者進入等待,
異步(async)
B在A之后執行,不過B可以忙別的事,
- 上面互斥的例子,B可以刷刷抖音,或者視線排隊在邊上坐著,等待A的通知,或者售票員喇叭的通知,
- 上面同步的第二個例子i,消費者可以刷刷抖音或者在家里躺著,等待生產者的通知,或者商店的短信,
異步的實作
- 創建(
spawn)一個執行緒等待資源,主執行緒干別的事, - 執行緒獲取資源后通過回呼函式執行下一步,或者通過訊息佇列通知主執行緒,
簡化異步
這個困惑了我很久,如今網上的熱詞如異步執行緒, 阻塞佇列,天生并發等等等其實都是說的一件事: 簡化異步,眾所周知,作業系統已經幫我們實作了執行緒間異步,以linux為例, 執行緒天生就有就緒, 等待, 運行三個狀態,并且內核也維護了一個阻塞佇列,如果從頭實作異步,我們需要
- 創建自己的訊息佇列
- 時不時自己管理執行緒
- 甚至維護自己的執行緒池
而這些所謂的高并發語言, 都維護了一個自己的阻塞佇列,維護了自己的訊息佇列,維護了自己的執行緒池,維護了自己的并發io庫,并且擁有自己的異步語法糖,
簡化異步的編程范式
以nodejs為例分析簡化異步寫法的歷史與發展,其他的大同小異,
-
回呼函式時期,為了支持異步,于是將IO進行封裝,尾巴帶了一個回呼函式
// 同步IO函式 function trueIO(): ResultType { ... return res; } // 將IO操作和trueIO裝入一個執行緒,并加入阻塞佇列 // 從而不會阻止主執行緒 function trueIOAsync(callback: (result: ResultType) => void) { block_queue.add(function () { const res = trueIO(); callback(trueIO); }) } // 沒有實作阻塞佇列的語言可能會用spawn function trueIOAsync(callback: (result: ResultType) => void) { spawn(function () { const res = trueIO(); callback(trueIO); }) } // 呼叫異步IO函式 trueIOAsync(function (res) { console.log(res); })注:這個
trueIO和block_queue.add是我假設的函式 -
Promise時期,后來有人覺得回呼函式不太優雅,加上動態語言,函式式思想,
builder模式,鏈式呼叫的流行,就改變了封裝方法,Promise應運而生class Promise { callback: ((value: any) => void)[]; new(fn: ((result: ResultType) => void, r(err: ErrType) => void) => void) { fn(this.innerResolve); } then<T>(fn: (value: T) => void) { this.callback.push(fn); return this; } innerResolve(result) { this.callback.forEach(fn => fn(result)); } }這樣就能夠統一管理回呼函式,于是可以把回呼函式封裝成鏈式呼叫
// 封裝異步IO函式,加入阻塞佇列 function trueIOPromise() { return new Promise(function (res) { block_queue.add(function () { const res = trueIO(); res(res); }); }) // 呼叫異步IO函式 trueIOPromise().then((res) {console.log(res) }); }上面是比較底層的Promise,我們也可以畫蛇添足通過異步回呼函式構造
Promisefunction trueIOPromise() { return new Promise(function (res) { trueIOAsync(res(res)); }); } -
async, await,有人覺得
new Promise(...)依舊不夠優雅,因此出現了async和await,// 借用上面的trueIOPromise() async asyncFn() { return await trueIOPromise(); } // 經過轉譯后 function asyncFn() { return trueIOPromise(); } // 第二個例子 async asyncFn() { res = await trueIOPromise(); res++; return res; } // 經過轉譯后 function asyncFn() { return new Promise(function (resolve) { let res = undefined; trueIOPromise().then(function (result) { res = result; res++; resolve(res); }) }) }
以上就是三個階段,至于基于強大程序宏的rust的future, invoke也是大同小異
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/548636.html
標籤:其他
