Electron 多进程方案
这周大部分时间都是在开发一个基于 Electron 的客户端。
Electron 内置一个 chrome 内核,所以可以让我们像开发一个网站一样开发客户端。
在开发过程中,遇到了一个问题,就是我们的项目需要从本地加载 AI 的模型并进行计算。
看过我介绍 [浏览器结构那篇文章](http://sunra.top/2020/12/03/browser-architecture)的应该知道,在当前 chrome 结构下,网络进程与渲染进程是完全分开的,所以加载模型并不会 pend 住渲染进程,所以就没有在意。
但是事实证明我的页面还是会存在卡顿。
于是我就用开发者工具的 Performance 面板录制了一下,结果发现是模型加载完后的识别函数,它是一个 Promise,属于微任务,实际上是个协程,而且是个 CPU 密集型的协程。
于是这里我就产生了第一个问题:
协程会 pend 住进程吗?
要解决这问题,首先得明白协程是什么。
进程是什么
进程是系统资源分配的最小单位, 系统由一个个进程(程序)组成 一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。
文本区域存储处理器执行的代码
数据区域存储变量和进程执行期间使用的动态分配的内存;
堆栈区域存储着活动过程调用的指令和本地变量。
因此进程的创建和销毁都是相对于系统资源,所以是一种比较昂贵的操作。 进程有三个状态:
1. 等待态:等待某个事件的完成;
2. 就绪态:等待系统分配处理器以便运行;
3. 运行态:占有处理器正在运行。
进程是抢占式的争夺 CPU 运行自身,而 CPU 单核的情况下同一时间只能执行一个进程的代码,但是多进程的实现则是通过 CPU 飞快的切换不同进程,因此使得看上去就像是多个进程在同时进行.
通信问题: 由于进程间是隔离的,各自拥有自己的内存内存资源, 因此相对于线程比较安全, 所以不同进程之间的数据只能通过 IPC(Inter-Process Communication)进行通信共享.
线程又是什么
线程属于进程
线程共享进程的内存地址空间
线程几乎不占有系统资源 通信问题: 进程相当于一个容器,而线程而是运行在容器里面的,因此对于容器内的东西,线程是共同享有的,因此线程间的通信可以直接通过全局变量进行通信,但是由此带来的例如多个线程读写同一个地址变量的时候则将带来不可预期的后果,因此这时候引入了各种锁的作用,例如互斥锁等。
同时多线程是不安全的,当一个线程崩溃了,会导致整个进程也崩溃了,即其他线程也挂了, 但多进程而不会,一个进程挂了,另一个进程依然照样运行。
进程是系统分配资源的最小单位
线程是 CPU 调度的最小单位
由于默认进程内只有一个线程,所以多核 CPU 处理多进程就像是一个进程一个核心
什么是协程
协程的概念是相对多进程或者多线程来说的,他是一种协作式的用户态线程
1. 与之相对的,线程和进程是以抢占式执行的,意思就是系统帮我们自动快速切换线程和进程来让我们感觉同步运行的感觉,这个切换动作由系统自动完成
2. 协作式执行说的就是,想要切换线程,你必须要用户手动来切换 协程为什么那么快原因就是因为,无需系统自动切换(系统自动切换会浪费很多的资源),而协程是我们用户手动切换,而且是在同一个栈上执行,速度就会非常快而且省资源。
但是,协程有他自己的问题:协程只能有一个进程,一个线程在跑,一旦发生 IO 阻塞,这个程序就会卡住。
所以我们要使用协程之前,必须要保证我们所有的 IO 都必须是非阻塞的。也就是需要异步。
意思是多个线程互相协作,完成异步任务。
CPU 的多核多线程指的是什么
计算机的 cpu 物理核数是同时可以并行的线程数量(cpu 只能看到线程,线程是 cpu 调度分配的最小单位),由于超线程技术,实际上可以并行的线程数量通常是物理核数的两倍,这也是操作系统看到的核数。我们只 care 可以并行的线程数量,所以之后所说的核数是操作系统看到的核数,所指的核也是超线程技术之后的那个核(不是物理核)。
如果计算机有多个 cpu 核,且计算机中的总的线程数量小于核数,那线程就可以并行运行在不同的核中,如果是单核多线程,那多线程之间就不是并行,而是并发,即为了均衡负载,cpu 调度器会不断的在单核上切换不同的线程执行,但是我们说过,一个核只能运行一个线程,所以并发虽然让我们看起来不同线程之间的任务是并行执行的,但是实际上却由于增加了线程切换的开销使得代价更大了。如果是多核多线程,且线程数量大于核数,其中有些线程就会不断切换,并发执行,但实际上最大的并行数量还是当前这个进程中的核的数量,所以盲目增加线程数不仅不会让你的程序更快,反而会给你的程序增加额外的开销。
结论
从这个角度理解,协程其实就是把线程的切换权利交给了代码,也就是用户。
而同一个进程同时只能有一个线程执行,所以说 JavaScript 的单线程就会 pend 住渲染进程,所以协程其实也会 pend 住进程。
如何解决?
既然我有一个需要定时执行的 CPU 密集型的协程可能会 pend 住我当前的 JavaScript 线程进而 peng 住渲染进程,那我想办法单独开一个进程来执行这个任务不就好了?
那 Electron 如何搞多进程呢?
你去翻阅官方文档,并不会找到专门的多进程解决方案,只有多线程的方案,叫做 Web Worker,这个东西我也是第一次看到,有兴趣的可以自己查一下资料,用起来并不难,也不是 Electron 的接口,而是 JavaScript 提供的。
但是这并不能解决我们的问题,因为它是另开一个线程,如果我们的 CPU 是单核单线程的,并没有什么用,我们需要的是另开一个进程。
虽然我们很少有电脑是单核单线程了,但是 Web Worker 的使用是需要把参数深复制过去,而这个检测工具的入参需要 video 的视频流,是没法深复制的,会抛出异常,所以并不合适。
那我们的问题怎么解决呢?
其实 Electron 自身就是个多进程的架构,就像 Chrome 会为每个不同站的网页单独一个渲染进程一样,Electron 也能做到,我们只需要 new 一个新的 BrowserWindow 就可以了,然后让我们的检测任务在单独的渲染进程中工作,将结果通过 Electron 提供的 IPC 通信接口发送给主渲染进程就好。
参考链接:
https://juejin.cn/post/6844903607892967432
版权声明: 本文为 InfoQ 作者【将儒】的原创文章。
原文链接:【http://xie.infoq.cn/article/024b46eb10b915a2d08b45944】。文章转载请联系作者。
评论