原文地址:https://github.com/Microsoft/...node
本文介绍 Napa.js 的核心概念,带领你们探索 Napa.js 是如何运转起来的。关于它的由来和开发初衷,能够阅读 这篇文章git
Zone 是 Napa.js 中的核心概念,它是执行 JavaScript 代码的基本单元,全部涉及多线程相关的内容都离不开 Zone 这个概念。一个进程能够包含多个 zone,而每一个 zone 又由多个 JavaScript Worker 组成。github
在 zone 内部的全部 worker 都是类似的:他们加载相同的代码,几乎以相同的方式处理 broadcast
和 execute
请求,你没法指定执行某一个特定 worker 中的代码。在不一样 zone 之间的 worker 是彻底不一样的:他们加载不一样的代码,或者虽然加载相同代码但以不一样的策略执行,例如堆栈大小不一样、安全策略不一样等。应用会利用多个 zone 来加载不一样的策略。npm
有两种类型的 zone:数组
这样划分让你既能够用 Napa zone 处理繁重的计算事务,也能够用 Node zone 处理 IO 事务。同时 Node zone 也是对 Napa zone 没法完整支持 Node API 的一种补充。xcode
如下代码建立了一个包含 8 个 worker 的 Napa zone:promise
var napa = require('napajs'); var zone = napa.zone.create('sample-zone', { workers: 8 });
如下代码演示如何访问 Node zone:安全
var zone = napa.zone.node;
在 zone 上能够作两种类型的操做:多线程
broadcast
来启动应用、预加载一些数据或者修改应用设置。execute
一般是用来作实际业务的。Zone 的操做采用“先进先出”的策略,但 broadcast
比 execute
优先级更高。dom
如下代码演示了使用 broadcast
和 execute
完成一个简单的任务:
function foo() { console.log('hi'); } // This setups function definition of foo in all workers in the zone. zone.broadcast(foo.toString()); // This execute function foo on an arbitrary worker. zone.execute(() => { global.foo() });
因为 V8 不适合在多个 isolate 间执行 JavaScript 代码,每一个 isolate 管理本身内部的堆栈。在 isolate 之间传递值须要封送/拆收(marshalled/unmarshalled),载荷的大小和对象复杂度决定着通讯效率。全部 JavaScript isolate 都属于同一个进程,且原生对象能够被包装成 JavaScript 对象,咱们尝试在此基础上为 Napa 设计一种高效传输数据的模式。
为了实现上述模式,引入了如下概念:
可传输类型是指能够在 worker 中自由传输的 JavaScript 类型。包括
Transportable
接口的对象(TypeScript class)Store API 用于在 JavaScript worker 中共享数据。当执行 store.set
时,数据被封送到 JSON 并存储在进程的堆栈中,全部线程均可以访问;当执行 store.get
时,数据被拆收出来。
如下代码演示如何利用 store 共享数据:
var napa = require('napajs'); var zone = napa.zone.create('zone1'); var store = napa.store.create('store1'); // Set 'key1' in node. store.set('key1', { a: 1, b: "2", c: napa.memory.crtAllocator // transportable complex type. }; // Get 'key1' in another thread. zone.execute(() => { var store = global.napa.store.get('store1'); console.log(store.get('key1')); });
尽管很方便,但不建议在同一个事务里用 store 传值,由于这样作不单单只传输了数据(还附带了别的事情,好比加锁)。另外,虽然有垃圾回收机制,但开发者仍是应当在使用完数据后手动删除相应的 key。
执行 npm install napajs
安装。
在 OSX 系统安装后执行会报错,在 github issue 中里也有一样的提问,解决方法是按照官方的构建文档,本身手动构建。 最新版 v0.1.4 版本已经修复上述问题。
步骤以下:
安装先决依赖
Install C++ compilers that support C++14:
xcode-select --install
Install CMake:
brew install cmake
Install cmake-js:
npm install -g cmake-js
经过 npm 构建
npm install --no-fetch
下面是一个计算 π 值的例子,演示了如何利用多线程执行子任务。
var napa = require("napajs"); // Change this value to control number of napa workers initialized. const NUMBER_OF_WORKERS = 4; // Create a napa zone with number_of_workers napa workers. var zone = napa.zone.create('zone', { workers: NUMBER_OF_WORKERS }); // Estimate the value of π by using a Monte Carlo method function estimatePI(points) { var i = points; var inside = 0; while (i-- > 0) { var x = Math.random(); var y = Math.random(); if ((x * x) + (y * y) <= 1) { inside++; } } return inside / points * 4; } function run(points, batches) { var start = Date.now(); var promises = []; for (var i = 0; i < batches; i++) { promises[i] = zone.execute(estimatePI, [points / batches]); } return Promise.all(promises).then(values => { var aggregate = 0; values.forEach(result => aggregate += result.value); printResult(points, batches, aggregate / batches, Date.now() - start); }); } function printResult(points, batches, pi, ms) { console.log('\t' + points + '\t\t' + batches + '\t\t' + NUMBER_OF_WORKERS + '\t\t' + ms + '\t\t' + pi.toPrecision(7) + '\t' + Math.abs(pi - Math.PI).toPrecision(7)); } console.log(); console.log('\t# of points\t# of batches\t# of workers\tlatency in MS\testimated π\tdeviation'); console.log('\t---------------------------------------------------------------------------------------'); // Run with different # of points and batches in sequence. run(4000000, 1) .then(result => run(4000000, 2)) .then(result => run(4000000, 4)) .then(result => run(4000000, 8))
运行结果以下,当设置为 1 组、2 组、4 组子任务并行计算时,能够看出执行时间有明显提高,当设置为 8 组子任务并行计算时,因为没有更多的空闲 worker 资源,也就没有明显的执行时间的提高。
# of points # of batches # of workers latency in MS estimated π deviation --------------------------------------------------------------------------------------- 40000000 1 4 1015 3.141619 0.00002664641 40000000 2 4 532 3.141348 0.0002450536 40000000 4 4 331 3.141185 0.0004080536 40000000 8 4 326 3.141620 0.00002724641
var napa = require("napajs"); // Change this value to control number of napa workers initialized. const NUMBER_OF_WORKERS = 4; // Create a napa zone with number_of_workers napa workers. var zone = napa.zone.create('zone', { workers: NUMBER_OF_WORKERS }); /* Fibonacci sequence n: | 0 1 2 3 4 5 6 7 8 9 10 11 ... ------------------------------------------------------------------------- NTH Fibonacci: | 0 1 1 2 3 5 8 13 21 34 55 89 ... */ function fibonacci(n) { if (n <= 1) { return n; } var p1 = zone.execute("", "fibonacci", [n - 1]); var p2 = zone.execute("", "fibonacci", [n - 2]); // Returning promise to avoid blocking each worker. return Promise.all([p1, p2]).then(([result1, result2]) => { return result1.value + result2.value; }); } function run(n) { var start = Date.now(); return zone.execute('', "fibonacci", [n]) .then(result => { printResult(n, result.value, Date.now() - start); return result.value; }); } function printResult(nth, fibonacci, ms) { console.log('\t' + nth + '\t' + fibonacci + '\t\t' + NUMBER_OF_WORKERS + '\t\t' + ms); } console.log(); console.log('\tNth\tFibonacci\t# of workers\tlatency in MS'); console.log('\t-----------------------------------------------------------'); // Broadcast declaration of 'napa' and 'zone' to napa workers. zone.broadcast(' \ var napa = require("napajs"); \ var zone = napa.zone.get("zone"); \ '); // Broadcast function declaration of 'fibonacci' to napa workers. zone.broadcast(fibonacci.toString()); // Run fibonacci evaluation in sequence. run(10) .then(result => { run(11) .then(result => { run(12) .then(result => { run(13) .then(result => { run(14) .then(result => { run(15) .then(result => { run(16) }) }) }) }) }) })
运算结果
Nth Fibonacci # of workers latency in MS ----------------------------------------------------------- 10 55 4 10 11 89 4 13 12 144 4 15 13 233 4 22 14 377 4 31 15 610 4 50 16 987 4 81