這篇文章帶大家了解一下Node中的非同步實作與事件驅動,希望對大家有幫助!
#電腦中的一些任務一般可以分割為兩個類別,一個類別叫做IO密集型,一個叫做運算密集型;對於運算密集型的任務,只能不斷榨乾CPU的效能,但是對於IO密集型的任務來說,理想情況下卻並不需要,只需要通知IO設備進行處理,過一段時間再來拿去數據就好了。 【相關教學推薦:nodejs影片教學 、程式設計影片】
對於某些場景有一些互不相關的任務需要完成,現行的主流方法有以下兩種:
剛才講了
node
在多工處理的方案,但是node
內部想要實現卻不容易,下面介紹作業系統的幾個概念,方面後續大家更好理解,後面再說一講異步的實作以及node的事件循環機制:
作業系統中一切皆文件,輸入輸出設備同樣被抽象化為了文件,核心在執行IO操作時,透過文件描述符進行管理
重複檢查IO狀態
來完成完整資料的取得
檔案描述子上的事件狀態
來進行判斷,相對來說消耗更少;缺點就是它採用了一個1024長度的陣列來存儲狀態,所以它最多可以同時檢查1024個檔案描述符
select
的限制,poll
改進為鍊錶的存儲方式,其他的基本上都一致;但是當檔案描述符較多的時候,它的性能還是非常低下的
linux
下效率最高的IO事件通知機制,在進入輪詢的時候如果沒有檢查IO事件,將會進行休眠,直到事件發生將它喚醒
epoll
類似,不過僅在FreeBSD系統下存在
#利用了事件來降低對CPU的耗用,但休眠期間CPU幾乎是閒置的;我們期待的非同步IO應該是應用程式發起非阻塞調用,無須透過遍歷或事件喚醒等方式輪詢,可以直接處理下一個任務,只需IO完成後透過訊號或回調將資料傳遞給應用程式即可。
node中對於非同步IO的實作
對非同步IO的實作是透過多執行緒實現的。可能會混淆的地方就是node
內部雖然是多執行緒的,但是我們程式設計師開發的JavaScript
程式碼卻只是運行在單一執行緒上的。 <p><code>node
透過部分執行緒進行阻塞IO或非阻塞IO加上輪詢技術來完成資料獲取,讓一個執行緒進行計算處理,透過執行緒之間的通訊將IO得到的資料傳遞,這就輕鬆實現了非同步IO的模擬。
除了非同步IO,電腦中的其他資源也適用,因為linux中一切皆文件,磁碟、硬體、套接字等幾乎所有電腦資源都被抽象化為了文件,接下來介紹對電腦資源的呼叫都以IO為例子。
在進程啟動時,node
便會建立一個類似與while(true)
的循環,每執行一次循環體的過程我們成為Tick
;
下方為node
中事件循環流程圖:
很簡單的一張圖,簡單解釋一下:就是每次都從IO觀察者裡面獲取執行完成的事件(是個請求對象,簡單理解就是包含了請求中產生的一些數據),然後沒有回調函數的話就繼續取出下一個事件(請求物件),有回呼就執行回呼函數
#註:不同平台有不同的細節實現,這張圖隱藏了相關平台相容細節,例如windows下使用IOCP中的提交執行狀態,透過GetQueuedCompletionStatus
取得執行完成的請求,並且IOCP內部實作了執行緒池的細節,而linux等平台透過eopll實作這個過程,並在
與
setInterval
#除了IO等電腦資源需要非同步呼叫之外,其他非同步API
:#setTimeout
setInterval
process.nextTick
只是不需要IO執行緒池的參與######### ############n######)########################你有考慮過這個問題嗎,為什麼定時器不需要線程池的參與了呢,如果你理解了之前章節對於異步IO實現原理的話,相信你應該能解釋出來,這裡簡單說說原因來加深記憶:###:##setTimtouto
node
中的IO執行緒池是用來呼叫IO並等待資料回傳(看具體實作)的一種方式,它使JavaScript
單執行緒得以異步呼叫IO,並且不需要等待IO執行完成(因為是IO線程池做了),並且能獲取到最終的資料(透過觀察者模式:IO觀察者從線程池獲取執行完成的事件,事件循環機制執行後續的回呼函數)
上述這段話可能有點簡略,如果你還不明白,可以看下之前的那幾種圖~
process .nextTick
與setImmediate
這兩個函數都是代表立即非同步執行一個函數,那為什麼不用setTimeout(() => { . .. }, 0)
來完成呢?
process.nextTick
更加輕量輕量具體來說:我們在每次呼叫process.nextTick
的時候,只會將回調函數放入佇列中,在下一輪Tick
時取出執行。定時器中採用紅黑樹的方式時#,nextTick
為
#那
process.nextTick與
setImmediate
process.nextTick的回呼執行優先權高於
setImmediate
#process.nextTick的回呼函數保存在一個陣列中,每輪事件循環下全部執行,
setImmediate的結果則是保存在鍊錶中,每輪迴圈依序執行第一個回調
注意:之所以
process.nextTick
的回呼執行優先權高於setImmediate
,因為事件循環對觀察者的檢查是有順序的, process.nextTick
屬於idle
觀察者,
對於網路套接字的處理,
#高效能伺服器
node
也應用到了非同步IO,網路套接字上偵聽到的請求都會形成事件交給IO觀察者,事件循環會不停地處理這些網路IO事件,如果我們在每進程-->每個請求
每個執行緒-->每個請求
經典問題--雪崩問題的解決:
問題描述:伺服器在剛啟動時,快取無數據,如果訪問量龐大,同一條SQL
#會被傳送到資料庫中反覆查詢,影響效能。
解決方案:
const proxy = new events.EventEmitter(); let status = "ready"; // 状态锁,避免反复查询 const select = function(callback) { proxy.once("selected", callback); // 绑定一个只执行一次名为selected的事件 if(status === "ready") { status = "pending"; // sql db.select("SQL", (res) => { proxy.emit("selected", res); // 触发事件,返回查询数据 status = "ready"; }) } }
使用once
將所有請求的回呼都壓入了事件佇列中,利用其只執行一次就會將監視器移除的特點,保證每一個回呼函數只會被執行一次。對於相同的SQL語句,保證在同一個查詢開始到結束的過程中永遠只有一次。新到來的相同呼叫只需在佇列中等待資料就緒即可,一旦查詢到結果,得到的結果就可以被這些呼叫共同使用。
更多程式相關知識,請造訪:程式設計教學! !
以上是聊聊Node中的非同步實作與事件驅動的詳細內容。更多資訊請關注PHP中文網其他相關文章!