跳到主要內容

Electron 內部原理:訊息迴圈整合

·3 分鐘閱讀時間

這是說明 Electron 內部原理系列文章的第一篇。這篇文章介紹了 Node 的事件迴圈如何與 Electron 中的 Chromium 整合。


過去曾多次嘗試使用 Node 進行 GUI 程式設計,例如用於 GTK+ 綁定的 node-gui 和用於 QT 綁定的 node-qt。但它們都無法在生產環境中運作,因為 GUI 工具組有自己的訊息迴圈,而 Node 使用 libuv 作為自己的事件迴圈,且主執行緒一次只能執行一個迴圈。因此,在 Node 中執行 GUI 訊息迴圈的常見技巧是以非常小的間隔在計時器中泵送訊息迴圈,這會使 GUI 介面回應緩慢並佔用大量 CPU 資源。

在 Electron 的開發過程中,我們遇到了同樣的問題,儘管方式相反:我們必須將 Node 的事件迴圈整合到 Chromium 的訊息迴圈中。

主程序和渲染程序

在我們深入研究訊息迴圈整合的細節之前,我將首先解釋 Chromium 的多程序架構。

在 Electron 中,有兩種程序類型:主程序和渲染程序(這實際上是極度簡化的說法,如需完整檢視,請參閱 多程序架構)。主程序負責 GUI 工作,例如建立視窗,而渲染程序僅處理執行和渲染網頁。

Electron 允許使用 JavaScript 控制主程序和渲染程序,這表示我們必須將 Node 整合到這兩個程序中。

用 libuv 取代 Chromium 的訊息迴圈

我的第一次嘗試是用 libuv 重新實作 Chromium 的訊息迴圈。

對於渲染程序來說很容易,因為它的訊息迴圈只監聽檔案描述器和計時器,而我只需要用 libuv 實作介面即可。

然而,對於主程序來說,這要困難得多。每個平台都有自己種類的 GUI 訊息迴圈。macOS Chromium 使用 NSRunLoop,而 Linux 使用 glib。我嘗試了許多駭客技巧來從原生 GUI 訊息迴圈中提取底層檔案描述器,然後將它們饋送到 libuv 進行迭代,但我仍然遇到了無法運作的邊緣案例。

因此,最後我新增了一個計時器,以小間隔輪詢 GUI 訊息迴圈。結果,程序佔用了恆定的 CPU 使用率,並且某些操作有很長的延遲。

在單獨的執行緒中輪詢 Node 的事件迴圈

隨著 libuv 的成熟,現在可以採用另一種方法。

後端 fd 的概念被引入 libuv,它是一個檔案描述器(或控制代碼),libuv 輪詢它以獲取其事件迴圈。因此,透過輪詢後端 fd,可以在 libuv 中有新事件時收到通知。

因此,在 Electron 中,我建立了一個單獨的執行緒來輪詢後端 fd,並且由於我使用的是系統呼叫進行輪詢而不是 libuv API,因此它是執行緒安全的。每當 libuv 的事件迴圈中有新事件時,就會將訊息發布到 Chromium 的訊息迴圈,然後 libuv 的事件將在主執行緒中處理。

透過這種方式,我避免了修補 Chromium 和 Node,並且相同的程式碼在主程序和渲染程序中都使用。

程式碼

您可以在 electron/atom/common/ 下的 node_bindings 檔案中找到訊息迴圈整合的實作。它可以輕鬆地重複用於想要整合 Node 的專案。

更新:實作已移至 electron/shell/common/node_bindings.cc