26

Blink Worker纸上谈兵 | ¥ЯႭ1I0

 4 years ago
source link: https://yrq110.me/post/front-end/contents-about-blink-web-worker/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

简单看了下chromium文档中关于worker的除基本使用之外的部分内容,虽然没什么用

  • worker类型
  • 进程与线程模型

官方设计文档的优点是只言片语就能描述出核心内容。

Blink中的Web Workers

Web Worker是一个Web特性,该特性提供一个后台JS上下文并允许在后台运行任意脚本,通常运行在与主UI线程分离的一个线程中。

HTML5 Workers: Dedicated Worker 与 Shared Worker

Dedicated worker 与 shared worker是两种"标准"worker,作为HTML5标准中的一部分,其他种类的worker都是基于这些worker设计的。

Dedicated worker 或直接称做“worker”,是最基础的worker,它会在与创建者文档绑定的后台上下文中,执行一个脚本。一个worker的生命周期基本上与其创建者的文档是一致的。

Shared worker 通过脚本的URL标识,可以由运行在同一域下的多个文档(站点)共享。当一个文档调用SharedWorker的构造器时,它会创建一个新的worker或连接至一个已存在的worker(若相同URL脚本的worker已经在运行)。worker上下文会在至少存在一个活跃文档的情况下保持存活。

workers.png

Service Workers

Service worker是一种强大的Web平台新特性,它允许脚本作为一个网络代理在后台运行,比如拦截一系列关联文档的请求。

service worker有一点类似shared worker,它们都可以被运行在同一域下的多个文档共享,但service worker的生命周期并不与任何文档绑定。

service worker以事件驱动的方式执行任务,即无论worker何时接收到事件,它都会启动(在之后被终止)

“Nested” Workers

即嵌套worker。HTML5 Worker(或其他worker,如service worker)可以由另一个worker创建与初始化,这些worker被称为嵌套worker(nested workers)。在2015年10月(ps:该文档撰写的时间)的Chromium/Blink中是不支持的(tracking bug: crbug.com/31666)。

根据文档中的描述,一个负责worker上下文的js上下文(比如一个worker的创建者文档)会被简记为父级文档关联文档,这个js上下文也可以是一个worker(在嵌套worker中的情况)。

worker上下文

worker上下文 通常是指一个后台的JS上下文,即worker运行的地方。一般worker上下文会运行在一个与主线程不同的线程,被称做“worker线程”。

worker线程

worker线程 指的是worker的js上下文(或称worker上下文)运行所在的线程。当一个新worker被创建时则worker线程即被创建,当worker关闭或被干掉时线程也会关闭。

worker线程有时也指Blink中WorkerThread类,该者通常对应一个底层平台的线程(由Blink中的WebThread表示),而有时则不是(比如compositor worker的情况)。下面会介绍worker不同的线程模型。

worker对象

worker对象 通常是指一个JS的“Worker”对象,通过该对象实现worker与其关联文档的通信。需要注意的事,worker对象是在worker的父级上下文(或关联上下文)中初始化的,这意味着它在父级上下文的线程中进行活动,该线程在多数情况下是主线程。

worker全局域

worker全局域 指的是一个worker js上下文的全局作用域(window是document中的JS全局域),worker全局域在Web Worker标准中被定义为WorkerGlobalScope接口。

WorkerGlobalScope接口暴露出了可用的Web worker API,但并不是所有的window域下的API在worker全局域都是可用的,有些API仅在worker中可用(Web Worker可用API列表)。

下面这张图表现出了worker上下文、worker线程、worker对象与worker全局念之间的关系:

terminology.png

虽然不同类型的worker具有不同的生命周期及进程模型,不过一般都是以下面的过程启动的:

  1. 文档创建一个新的worker对象,来实例化一个新的worker
  2. 接着worker对象会加载worker脚本
  3. 一旦脚本加载完成,则一个新的worker线程会被创建,该线程的主函数会配置一个新的worker全局作用域作为worker脚本的全局作用域
  4. worker线程会启动循环,并注入脚本

对于不同类型的worker其终止行为也不同,当发现下列情况时HTML5 worker会被终止:

  • worker显式地调用WorkerGlobalScope#close方法
  • 父级文档中调用Worker#terminate方法(仅对于dedicated worker)
  • 所有关联文档均关闭或变为不活跃状态,或:
  • 若文档切断了与worker对象的引用,则worker上下文中将不存在任何等待的活动,并且GC会启动并收集该worker对象。

除了第一种方法(WorkerGlobalScope#close)外,其他终止行为均由主线程触发,因此需要停止worker线程中的执行。

更多的有关生命周期的规范可以参考标准文档

进程与线程模型

进程模型 线程模型
Dedicated Worker 进程内 运行在自身线程 (Worker上下文 : 线程数 = 1:1)
Shared Worker 进程外 运行在自身线程 (Worker上下文 : 线程数 = 1:1)
Service Worker 进程外 运行在自身线程 (Worker上下文 : 线程数 = 1:1)

进程模型(Process Model)

Worker可以根据进程模型粗糙的分为两类: 进程内(in-process)worker与进程外(out-of-process)worker。

  • 进程内worker运行在与它们关联文档所在的同一进程中,因此它们仅需在文档中单纯的"添加新的线程”
  • 进程外worker可能运行在一个与他们关联文档不同的进程中。通常若一个worker需要由多个文档共享时,由于处在不同进程的不同文档均需要与同一个worker通信,Blink/Chromium一般会将其实现为一个进程外worker

在实现方面,进程内worker与它们文档的通信可以在渲染进程中通过在worker线程与主线程之间上传任务来实现。而进程外worker则需要使用IPC来与它们的文档通信,不管这个worker是否运行在同一还是不同的进程中。

线程模型(Thread Model)

几乎所有的worker都运行在它们自己的线程中(worker上下文 : worker线程 = 1:1的关系),不过也有例外: Compositor Worker运行在一个上下文无关、与进程对应的单例线程中,因此其worker上下文与worker线程关系为N:1。而Houdini Worker在计划中是与文档运行在同一线程(即worker与文档共享同一线程)

非主线程资源拉取(Off-the-main-thread fetch)

所有worker子资源与一些顶级脚本,均在非主线程(比如worker/worklet线程)中拉取。

在worker/worklet线程中存在两种网络资源fetch方式: insideSetting fetch与outsideSettings fetch。术语insideSettings与outsideSettings来自HTMLWorklet标准。

insideSettings fetch

insideSettings fetch指子资源拉取。

在标准中,insideSettings对应WorkerOrWorkletGlobalScope中的worker环境配置对象

在实现中,insideSettings直接对应了WorkerOrWorkletGlobalScope。WorkerOrWorkletGlobalScope::Fetcher()对应WorkerFetchContext,使用WorkerOrWorkletGlobalScope::GetContentSecurityPolicy()。

目前所有的子资源拉取均为非主线程行为。

outsideSettings fetch

outsideSettings fetch指非主线程的顶级worker/worklet脚本拉取。

在标准中,outsideSettings为worker父级上下文的环境配置对象。

在实现中,outsideSettings应该对应文档(或嵌套worker的WorkerOrWorkletGlobalScope),但worker线程由于线程限制无法访问这些对象,因此我们会传递一个包含这些跨线程信息的快照FetchClientSettingsObjectSnapshot,并在worker线程中创建ResourceFetcher, WorkerFetchContext和ContentSecurityPolicy(分离在insideSettings fetch中使用的对象)。他们的行为类似父级上下文,通过WorkerOrWorkletGlobalScope::CreateOutsideSettingsFetcher()使用。

注意,在非主线程拉取不可用的场景下(如classic worker),worker脚本会在主线程拉取,因此不会牵扯到WorkerOrWorkletGlobalScope与worker线程。

在Worker中使用ES模块

目前已知只有chrome的80版本后的dedicated worker中支持该特性。

有三种方式可以在dedicated worker中使用ES模块

  1. Worker构造函数

该方法在Worker构造器中的第二个参数设置type选项即可。将type设置为’module'后,worker的顶级脚本(top-level srcipt, 即最先执行的脚本)会被作为Module Script执行。

<script>
const worker = new Worker('module-worker.js', { type: 'module' });
</script>

在上面的例子中,就不在/标签中设置type='module'也没有问题。

  1. 静态导入(static import)

第二种是在worker内的静态导入方法

// module-worker.js
import * as module from './hello-module.js';
module.SayHello();

由于静态导入只能在Module Script中使用,因此需要结合第一种方法,将Module Script作为worker顶级脚本启动。下面是一个将Classic Script作为顶级脚本启动而静态导入失败的例子,当worker启动时自身会报错。

<script>
// Worker会被当做普通脚本执行
const worker = new Worker('module-worker.js');
worker.onerror = e => {
  // 这里会触发错误事件处理器
};
</script>
  1. 动态导入(dynamic import)

第三种是在worker内的动态导入方法

// In module-worker.js
import('./hello-module.js')
  .then(module => module.SayHello());

动态导入在作为Classic Script启动的worker内也可以使用

<script>
const worker = new Worker('module-worker.js');
worker.onerror = e => {
  // 这里不会触发错误事件处理器
};
</script>

行内脚本的使用方式

针对dedicated worker

<script id="my_worker" type="javascript/worker">
self.addEventListener("message", e => {
    self.postMessage(`${e.data} from worker!`);
});
</script>
const blob = new Blob([document.querySelector('#my_worker').textContent]);
const worker = new Worker(window.URL.createObjectURL(blob));
worker.addEventListener("message", e => {
    console.log("Received: " + e.data);
});
worker.postMessage("hello");

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK