前言
本文是Chromium官方设计文档Blink Workers主旨内容的翻译,介绍ServiceWorker在内核层面的一些基本概念和线程模型。
一 Worker类型
Web Workers是一个Web平台特性,它提供了后台JS context,和允许任何脚本运行在后台,通常运行在一个独立的线程。Blink实现了几类worker:
- dedicated worker: 专用worker, 只能被创建它的JS访问。创建它的页面关闭, 它的生命周期就结束了。 一个文档可以有多个dedicated worker。
- shared worker:共享worker, 可以被同一域名下的JS访问。关联的页面都关闭时, 它的生命周期就结束了。多个文档可以对应同一个shared worker。
- service worker:事件驱动的worker, 生命周期与页面无关。关联页面未关闭时, 它也可以退出, 没有关联页面时, 它也可以启动。
- compositor worker:允许JS脚本处理UI的工作,比如,响应输入和更新视觉效果。它运行在非UI线程。
二 基本概念
- Worker context:worker脚本运行的后台JS上下文。
- Worker thread:worker context运行的线程,它通常是指blink中的WorkerThread类,对应一个底层平台线程(blink中的WebThread)。
- Worker object: 通常是指JS的worker对象,关联文档可以通过它与worker交互。它一般在它的parent context进行初始化,它通常运行在parent context的线程中,一般是主线程。
- Worker global scope:worker JS上下文的global scope(比如, window 是文档JS的global scope)。注意,window scope中APIs与worker global scope的APIs并不是一一对应。
它们之间的关系:
Document 可以new一个Worker object,Worker object会去加载worker script,worker script加载完成后, 会new Worker thread,Worker thread会创建Worker global scope,worker context运行在worker thread。
三 进程模型
Workers可以大致分为in-process workers 和 out-of-process workers。
In-process workers: 与它们对应的document(s)运行在同一进程, 因此,它们基本只是对document(s)增加不同的线程。
Out-of-process workers:可以运行在与document(s)不同的进程。通常如果worker需要由不同的documents共享,blink/chromium就会实现为out-of-process worker。
具体实现上,in-process workers可以在renderer进程中worker线程和主线程通过post task来实现交互,而out-of-process workers必须通过IPC进行通信,无论worker是否和文档运行在同一进程。
四 线程模型
大部分worker都运行在它们自己的线程(比如,worker context : worker thread = 1:1),而Compositor Worker例外,它是运行在per-process singleton thread(比如,worker context : worker thread = N:1)。
Out-of-process workers需要使用IPCs与关联的文档进行交互,而IPC的基础设施在browser进程实现,所以它们一部分的代码需要在browser进程实现。
Process model | Thread model | |
Dedicated Worker | In-process | Run on its own thread |
Shared Worker | Out-of-process | Run on its own thread |
Service Worker | Out-of-process | Run on its own thread |
Compositor Worker | In-process | Share a per-process single thread within a process |
Isolated Worker | In-process | May run on the same thread as document thread |
五 代码目录说明
-
third_party/WebKit/Source/core/workers/*
-
Common worker code (e.g. WorkerThread.*, WorkerGlobalScope.*)
-
Dedicated worker code (e.g. DedicatedWorker*)
-
Shared worker code (e.g. SharedWorker*)
-
-
third_party/WebKit/Source/modules/serviceworkers/*
-
Service worker blink端的代码
-
-
third_party/WebKit/Source/modules/compositorworker/*
-
Compositor worker bink端的代码
-
-
third_party/WebKit/Source/web/*Worker*
-
实现public/web/的类,browser端使用/交互的类 (e.g. ServiceWorkerGlobalScopeProxy, WebEmbeddedWorkerImpl, WebSharedWorkerImpl)
-
通过public/web/* 与browser端交互的类 (e.g. WorkerGlobalScopeProxyProviderImpl, ServiceWorkerGlobalScopeClientImpl)
-
-
third_party/WebKit/public/web/*Worker*
-
头文件的类,由Blink实现,给browser端使用 (e.g. WebEmbeddedWorker, WebSharedWorker, WebServiceWorkerContextProxy)
-
头文件的类,由browser端实现,给blink使用 (e.g. WebServiceWorkerContextClient)
-
-
content/{browser,child,common,renderer}/service_worker/*
-
service worker browser端的代码
-
-
content/{browser,renderer}/shared_worker/*
-
shared worker browser端的代码
-
-
content/child/worker_*
- 处理worker thread的browser端的代码。主要是从worker thread收发IPCs。比如,Worker context中的storage API需要和browser进程进行交互时通常使用这些类。
六 重要类说明
(1)Worker object classes
所有Worker object类都从AbstractWorker类派生,AbstractWorker接口定义为H5 workers的通用基础接口。
(2)Worker thread classes
WorkerThread: 代表一个worker线程,WorkerThread的实例一般会持有一个platform thread(通过WebThreadSupportingGC类持有WebThread,通过WorkerThread::backingThread访问)。
每个 WorkerThread 会使用一个v8::Isolate来完全隔离running worker script和main thread scripts。
WorkerBackingThread:代表workers的一个线程,它持有一个WebThreadSupportingGC和一个v8::Isolate。多个workers可以关联到一个WorkerBackingThread(比如,CompositorWorkers)。
注意:大部分WorkerThread的子类,仅仅重写了createWorkerGlobalScope方法,而CompositorWorkerThread还重写了线程和v8::Isolate相关的一系列方法,从而可以做到多个CompositorWorkerThread共享一个per-process platform thread 和 v8::Isolate。
(3)Worker global scope classes
WorkerGlobalScope:通用的基础类,代码一个worker global scope。
注:前面章节为Blink Workers主旨内容的翻译
七 ServiceWorker进程模型
在多进程模型下, ServiceWorker会同时运行在renderrer进程和browser进程,其中ServiceWorkerThread运行在renderer进程,其他如ServiceWorkerVersion等则运行在browser进程。
打开两个相同域名的ServiceWorker页面,页面里的ServiceWorker会运行在同一renderrer进程中。
打开两个不同域名的ServiceWorker页面,页面里的ServiceWorker会运行在不同renderrer进程中。
如果浏览器已有同一域名的renderrer进程,新建一个ServiceWorker一般不会创建一个新的renderrer进程,而只会使用已有的renderrer进程。
我们看看,ServiceWorker创建renderrer进程的过程:
ServiceWorkerVersion::StartWorker
--> EmbeddedWorkerInstance::Start
--> ServiceWorkerProcessManager::AllocateWorkerProcess(bool can_use_existing_process)
// 如果ServiceWorkerVersion失败次数超过2,那么EmbeddedWorkerInstance会强制新建一个renderrer进程。
// 这里的失败一般是指ServiceWorker注册过程的失败。
--> ServiceWorkerProcessManager::FindAvailableProcess // 查找可用的进程
--> ServiceWorkerProcessManager::SortProcessesForPattern(const GURL& pattern)
// 按域名查找是否存在合适的renderrer进程
--> pattern_processes_.find(pattern) // ServiceWorker注册的流程会将pattern加入pattern_processes_
--> SiteInstance::GetProcess // 如果上面没有找到合适的renderrer进程, 就新建一个renderrer进程
从上面流程可以看到, ServiceWorker会根据一定的算法去决定是否使用新的renderrer进程。其中两个较重要的因素是,按域名分派进程,同一version失败次数不能太多,比如,不超过2(kMaxSameProcessFailureCount)。
参考文档
Blink Workers - Current architecture, implementation and future project ideas for Blink Workers