作者: 涂鸦-长生
来涂鸦工作: job.tuya.com/
很多人刚刚接触小程序时,经常会 “嫌弃” 它跟 web 相比阉割弱化的能力、跟 Vue 相比简单到过分的语法,甚至有的人还会认为小程序就是仗着自己的强大生态和用户在搞技术垄断!但是其实这个问题的根本在于和 Web 网站相比,微信小程序更需要考虑安全、性能等因素,因为需要考虑到开发者写的小程序不会对宿主微信产生安全的隐患,还要有尽量接近原生的用户体验。而小程序的双线程模型,自然也是基于这些原因来考虑的。那小程序双线程模型到底是什么?关于这一点,我们可以从大家比较熟悉的浏览器线程模型来理解。
浏览器进程
JavaScript 是单线程的,但浏览器不是单线程,实际上浏览器内部架构复杂,只是在处理 GUI 渲染线程和 JavaScript 逻辑脚本线程上用了相互、阻塞的管理模式。以我们最常见的 Chrome 浏览器为例,点击设置按钮后进入“更多工具” --> "任务管理器" 会看到浏览器的多个线程:
从图中可以看到,Chrome 上有多个进程,其中包括浏览器进程、GPU 进程、网络服务进程等通用进程,还有两个标签页进程,Chrome 给每个标签页开启了一个独立的渲染进程,每个进程之间的资源和行为互不共享,即便是某个标签页崩溃了也不会影响其他标签页。在每个标签页进程中,浏览器把不同的工作交给对应的线程,比如 GUI 渲染线程负责把 HTML 渲染成可视化的 UI;JavaScript 引擎线程负责解析和运行 JavaScript 代码逻辑;定时触发器线程负责处理 setTimeout/setInterval 定时器等。GUI 渲染进程和 JavaScript 引擎线程是互斥的,JavaScript 在执行期间会阻塞 UI 的渲染,甚至有可能脚本执行时间太长会由于页面长时间无响应然后崩溃,也正是因为 GUI 渲染进程和 JavaScript 引擎线程之间的互斥、阻塞线程管理方式,让一部分人认为浏览器是单线程的。
为啥 JavaScript 是单线程的
从运行机制的角度上来说,JavaScript 设计成单线程主要是避免多线程操作 DOM 造成 UI 冲突,如果多个线程操作同一个 DOM,浏览器很难判断最终的 UI 效果采用哪个线程的结果,虽然可以采用类似于 java 的锁机制来处理,但是这样一来就大大加大了 JavaScript 的复杂性。这也解释了为啥 GUI 渲染线程和 JavaScript 引擎是互斥的,JavaScript 有修改 DOM 的权限,不互斥就很容易打架,造成页面卡死。
从设计的角度上来说,当初设计 JavaScript 语言的时候定位的就是一门 “简单” 的语言,没有丰富的语言特性,也没有大一统的野心,更没有“包打天下”的虚拟机引擎,是真正的 “小而简洁”,从这个角度上来说,JavaScript 也不可能设计出多线程来,毕竟光多线程编程,都可以作为一门课程来讲。
当 JavaScript 代码被执行时,GUI 渲染线程会被挂起,等待 JavaScript 引擎线程空闲时再被执行,以免在渲染期间被 JavaScript 重复地修改 DOM 造成不必要的渲染压力。采用互斥的模式等待 JavaScript 代码执行完毕之后,可以保证页面渲染的是最终的执行结果。所以浏览器的空间时间(ldle)也成了衡量网站性能的指标之一,空闲时长多代表 JavaScript 逻辑不密集以及 DOM 改动频率低,浏览器可以更快速顺畅地响应用户的交互。如下图是涂鸦官网的空闲时间:
但是,这里又要说但是了,但是 HTML5 引入了 Web Worker,提供了多线程执行 JavaScript 代码的能力,允许主线程创建 Worker 线程,将一些任务分配给 Worker 线程执行,等 Worker 线程完成计算任务,再把结果返回给主线程。和 java 等后端的编程语言不同的是,Worker 线程与主线在地位上并不是一样的,而是一种主从多线程模型,而且 Worker 线程一旦新建成功,就会始终运行,不会被主线程上的动作所打断,这样虽然有利于随时响应主线程的通信,但是也造成了 Worker 比较耗费资源。
为啥小程序不用浏览器的线程模型
从上文来看,其实小程序也是可以使用类似的 Worker 线程来跑业务逻辑,用 webView 来渲染页面,从理论上来说这样是没问题,但是从小程序的以下几点来分析,却是有问题的。
从平台的角度来看,平台最核心的考量点是对外提供能力的前提下,保证自身平台的足够安全。比如涂鸦 IoT 平台,对外给开发者提供控制设备的能力,但是必须保证自己本身平台和数据足够安全。对于微信来说也是如此,小程序在微信这个宿主上跑,但是不能有全部的 web 能力的,不然如果被执行一些危险的、会超出微信控制的代码(比如 eval)。
从性能的角度上来看,浏览器的主线程与 Worker 之间的通信内容,可以是文本、也可以是对象。但是这种通信是传值而不是传地址,不管你传的基本数据类型还是引用数据类型,一律传值。这样的好处是 Worker 对通信内容的修改,不会影响主线程,坏处是影响效率,如果我要发一个 100MB ,主线程需要对原文件做一个拷贝,把值通过 postMessage 的方式给到 Worker 线程,整体传输的效率就会大大降低。而如果设计一个双线程模型,这两个线程是并列的,两个都是独立的主线程,传值让 Native 来做,而且因为渲染进程的角色是单一的,就是只负责渲染,不会有逻辑的代码,整体的架构和性能也将会更加清晰.
从开发的角度来看, Worker 线程里是无法获取 dom 节点的,但是在有些开发情境下,还是需要获取 Dom 节点,并做一些操作,所以 Worker 无法胜任,而在小程序的逻辑进程中,虽然腾讯是尽量不让我们去获取节点,但是还是有接口去异步的获取节点信息,完成一些特殊的操作。
从以上这几个角度上来看,小程序的双线程模型已经大致清晰了。小程序的双线程指的是渲染线程和逻辑线程,这两个线程分别承担 UI 渲染和执行 JavaScript 代码的工作。如下图所示
渲染线程使用 Webview 进行 UI 的渲染呈现。逻辑层独立为一个与 WebView 平行的线程,使用客户端提供的 JavaScript 引擎运行代码,ios 平台下使用的是 JavaScriptCore、安卓的是腾讯 X5 内核提供的 JsCore 环境以及 IDE 工具的 nw.js。渲染线程和逻辑线程是通过 Native 来通信吗,以事件驱动的方式来进行通信。
双线程模型通信
小程序的双线程通信是不是在渲染线程和逻辑线程之间直接传递数据或事件,而是用 Native 来作为媒介来进行转发,整个过程是比较典型的事件驱动模式:
- 渲染层通过与用户的交互触发特定的事件 event;
- event 通过 Native 传递给逻辑层
- 逻辑层收到触发某个事件,并通过一系列的逻辑处理、数据请求、接口调用等工作将加工好的数据通过 setData 的方法传递给渲染层
- 渲染层将 data 渲染为可视化的 UI
总结
小程序的双线程模型跟浏览器的双线程模型相比,解决了 Web Worker 性能问题的同时还实现了和 Web Worker 相同的线程安全,从性能和安全两个角度实现了提升。是受限于浏览器现有的进程和线程管理模式下,在小程序场景的一种改进架构方案。从了解小程序双线程模型上,对我们开发的工作也有一定的启示,比如如果我们要在小程序上性能有较大的提升,需要保证功能的前提下尽量使用结构简单的 UI,在尽量降低 JavaScript 逻辑的复杂度,减少执行时间,尽量减少 setData 的调用频次和携带的数据体量。
来涂鸦工作: job.tuya.com/
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!