”主进程“和“渲染进程”是Electron的两个核心的概念。
如果你之前做的是浏览器端JavaScript开发,多进程的概念对你来说可能是一个新的领域。
最初对我来说,这绝对是一个思维方式的转变,使用多进程可能意味着我们需要在开发过程中做出跟之前不同的设计抉择。
为什么Electron具有这种多进程架构?主进程职责是什么?渲染进程的职责是什么?它们之间如何实现通信?
首先,我们这里所说的“进程”是什么?
一个操作系统级别的进程,或者如Wikipedia所述,它是“正在执行的计算机程序的实例”。
我们启动一个Electron应用程序,然后在macOS中检查“活动监视器”,则可以看到与该程序关联的进程数。
“ Electron”是主进程,一个“ Electron Helper”是GPU进程,另一个“ Electron Helpers”是渲染进程。
这些进程中的每一个彼此并发运行。这里要记住的最重要的一点是,进程的内存和资源是相互隔离的。
举例来说,假设我有一个模块,该模块可以保存我的主进程和渲染进程所需的某些状态:
如果我在渲染进程中增加1,则渲染进程中的计数将为1,但在主进程中仍为0。
这两个进程不共享内存或状态。实际上,该模块有两个实例在运行。
为什么要多个进程?
此架构决策源自Chromium。Chromium在单独的进程中运行每个选项卡(即webContents实例),因此,如果一个选项卡遇到致命错误,则不会关闭整个应用程序。从这个意义上说,“ Chromium像操作系统一样构建,使用多个OS进程将网站彼此隔离,并与浏览器本身隔离。” 因此,每个进程“在其自己的地址空间中运行,由操作系统调度,并且可以单独失败”。当有人一步小心地写了一个无限循环,然后关闭正在运行的选项卡,而不是整个浏览器。这种体系结构要感谢这种弹性。还有安全原因。Chromium的多进程体系结构文档非常有趣:https://www.chromium.org/developers/design-documents/multi-process-architecture
主进程
主进程负责创建和管理BrowserWindow实例以及各种应用程序事件。它还可以执行诸如注册全局快捷方式,创建系统菜单和对话框,响应自动更新事件等操作。应用程序的入口点将指向将在主进程中执行的JavaScript文件。主进程以及所有node.js模块中都提供了一部分Electron API(请参见下图)。
docs声明:“基本规则是:如果模块与GUI或低级系统相关,则它应仅在主进程l中可用。” (请注意,此处的GUI是指本机GUI,而不是Chromium呈现的基于HTML的UI)。这是为了避免潜在的内存泄漏问题。
渲染进程
渲染过程负责运行应用程序的用户界面,换句话说,就是作为webContents实例的网页。渲染进程中提供了所有DOM API,node.js API和Electron API的子集(请参见下图)。
我曾经将BrowserWindow与渲染进程混合在一起。在窗口中包含webContents实例之前,实际上不会创建渲染进程。这有点挑剔,但我认为这很重要,并且知道它会导致更坚实的概念基础。另外,一个或多个WebContent可以位于一个窗口中。等等,一个或多个?是的,因为单个窗口可以承载多个Web视图,并且每个Web视图都是其自己的webContents实例和渲染进程。因此,例如,如果您的页面中包含2个Web视图,则将有3个渲染进程-一个用于托管2个Web视图的父级,然后一个用于每个Web视图。话虽如此,除非您要运行远程网页,否则无需使用Web视图,因此不必太在意该细节。
上图显示了每种进程中可用的Electron API。您还可以看到Node.js API全局可用,而渲染进程中只有DOM / Browser API。
如何在进程之间进行通信?
Electron使用进程间通信(IPC)在进程之间进行通信-与Chromium相同。
IPC有点像在网页和iframe或webWorker之间使用postMessage。
一般来说,发送的消息会带有频道名称和一些其他信息。
IPC可以在渲染进程和主进程之间双向通信。IPC默认情况下是异步的,但也具有同步API(例如Node.js中的fs)。
Electron还为提供了远程模块,例如,您可以使用主处理模块,就像Menu
渲染器中可用的一样。
不需要进行手动IPC调用,但实际上是在幕后,您是通过同步IPC调用向主进程发出命令。
使用devtron模块,我们可以观察到使用远程模块时发生的所有IPC调用。同步IPC调用可能会有性能缺陷。在许多情况下,可能还不错。
remote
用于打开对话框时,会发生多个同步IPC调用。
使用ipc或远程API的代码非常简单。一个基本的示例可以在这里找到: ccnokes / electron-tutorials-小样本电子应用程序的集合。
有没有一个方法是主进程和渲染进程都支持的?
是的,可以通过remote模块访问主进程API,例如:
IPC是否在底层使用了某些网络协议(例如tcp,http或更疯狂的东西)?
不。Chromium的IPC文档指出,它使用“命名管道”作为IPC的基础工具。命名管道比网络协议可以提供更快,更安全的通信。
“命名管道”类似于“无名管道”,当您执行类似操作时使用ls | grep foo
。命名管道很有趣,
您可以在此处查看示例:https : //github.com/ccnokes/node-fifo-example。
那么我在哪里进行CPU密集型工作?
我曾经认为 主进程 是“繁重工作”的理想之地,因为它不会阻塞UI。
实际上,这是错误的—如果我们在主进程中执行CPU密集型工作,它将锁定所有渲染进程。
因此,CPU密集型任务应在单独的进程中运行-而不是任何包含UI的现有渲染进程或主进程。
其实,最简单的方法是使用Electron-remote。Electron-remote非常棒,并且具有渲染进程任务池,该任务池将跨多个进程拆分和平衡作业。
这是一个简单的例子:
在这段代码中electron remore 最多创建4个BrowserWindow实例,所有这些实例都需要您的模块并运行它,然后协调进程之间的来回通信。
附:IPC通信的几种方式
数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间
共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
linux常用的进程间的通讯方式
(1)、管道(pipe):管道可用于具有亲缘关系的进程间的通信,是一种半双工的方式,数据只能单向流动,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
(2)、命名管道(named pipe):命名管道克服了管道没有名字的限制,同时除了具有管道的功能外(也是半双工),它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。
(3)、信号(signal):信号是比较复杂的通信方式,用于通知接收进程有某种事件发生了,除了进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。
(4)、消息队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺
(5)、共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
(6)、内存映射:内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
(7)、信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
(8)、套接字(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。