AyoでWorkerの実装が進んでいる
Chrome Dev Summitのため、サンフランシスコで書いています。
さて、最近、Ayo側でWorkerの動きが活発なのでそろそろまとめてみた。
昨日一日でこれだけ出ている...!
Ayo側
$ ./ayo --version v9.0.0-pre
実装者はAnna、過去にNodeにWorkerを入れようという動きもあったため、petkaantonov/io.jsからベースは引用されています。
例えば、initial implementationはこちら
この間、第一回目のコアチームミーティングが開催されたが、自分は参加してないので何が話し合われたのかわからないです。
後ほど、アップされるとのことなのでそれを待ちます。
ただ、WorkerがAyoのコアには入っているので、基本的にはStabilityを2まで持っていく方針だと思われます。
Stabilityとは? About this Documentation | Node.js v8.7.0 Documentation
これが最初のNodeとAyoとの技術的な優位性です。
Node側
NodeもAyoからのコミットを入れるかどうかのPRが上がっています。
ただ今現在、このPRの1つしか存在せず、進んでいないです。
もし、入れたいと思う人がいればここにコメントするとよさそう;)
Workerとは?
独立したスレッドで動作する環境を構築し、それらの間にメッセージチャンネルを構築します。
WorkerはCPU集約型のJavaScriptの操作を実行するのに便利です。
子プロセスやClusterとは違い、それらの間でWorkerはArrayBuffer
のインスタンス間の転送をしたり、
それらの間でSharedArrayBuffer
のインスタンスを共有をすることによりメモリを効率的に共有することが可能です。
Eventの説明は省きます
const worker = require('worker');
const { Worker, isMainThread, postMessage, workerData } = require('worker'); if (isMainThread) { module.exports = async function parseJSAsync(script) { return new Promise((resolve, reject) => { const worker = new Worker(__filename, { workerData: script }); worker.on('message', resolve); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`)); }); }); }; } else { const { parse } = require('some-js-parsing-library'); const script = workerData; postMessage(parse(script)); }
メソッド・変数
isMainThread
Workerスレッド内で実行されているかを返します。
実行されてなければ、true
を返します。
postMessage(value[, transferList])
Workerスレッド内でのみ、使用可能です。
worker.on('message')
を経由し、受信される親スレッドのWorkerインスタンスにメッセージを送信します。
threadId
現在のスレッドのIdを返します。
workerData
Workerコンストラクタに渡されるデータの複製を含む任意のJavaScriptデータ。
クラス
MessageChannel
このインスタンスは、非同期の双方向通信チャネルを示します。
独自のメソッドを持ちません。
const { MessageChannel } = require('worker'); const { port1, port2 } = new MessageChannel(); port1.on('message', (message) => console.log('received', message)); port2.postMessage({ foo: 'bar' }); // prints from L4: received { foo: 'bar' }
上記のコードは、終了しません。(exit)
ずっと待ち続けます。
MessagePort
非同期の双方向通信チャネルの一端を表します。
構造化データ、メモリ領域、および他のMessagePortsを異なるWorkerまたはVMコンテキスト間で転送するために使用できます。
これは、EventEmitterの拡張です。
また、EventTargetsではなくEventEmitterであるMessagePortsを除いて、この実装はブラウザのMessagePortsと同じです。
postMessage(value[, transferList])
Worker
独立したJavaScript実行スレッドを示します。
殆どのAyoのAPIは、この中で実行可能です。
new Worker(filename, options)
filename
filename
は絶対パスでなければなりません。
options.eval
がtrue
の場合、パスではなくJavaScriptコードを含む文字列となります。
options
- eval Workerがオンラインになった時に実行されるJavaScript。
- data
クローンされ、
require('worker').workerData
として利用可能となるJavaScript値。 複製は、Structured_clone_algorithmと同様に行われできない場合はエラーが飛ばされます(e.g. 関数が含まれている場合)
maxSemiSpaceSize スレッドのヒープのセミスペースのオプションのメモリ制限(MB単位)。
ほとんどのshort-lived objectsはここに含まれます。maxOldSpaceSize スレッドのメインヒープのオプションのメモリ制限(MB単位)。
const assert = require('assert'); const { Worker, MessageChannel, MessagePort, isMainThread } = require('worker'); if (isMainThread) { const worker = new Worker(__filename); const subChannel = new MessageChannel(); worker.postMessage({ hereIsYourPort: subChannel.port1 }, [subChannel.port1]); subChannel.port2.on('message', (value) => { console.log('received:', value); }); } else { require('worker').once('workerMessage', (value) => { assert(value.hereIsYourPort instanceof MessagePort); value.hereIsYourPort.postMessage('the worker is sending this'); value.hereIsYourPort.close(); }); } // prints from L8: received: the worker is sending this
Worker内の大きな違い
process.stdin
、process.stdout
、process.stderr
はnullに設定されています- domainモジュールは使えません
require('worker').isMainThread
はfalse
に設定されていますrequire('worker').postMessage()
が使用可能で、require( 'worker')on( 'workerMessage')
がエミットされますprocess.exit()
はプログラム全体を停止せずに単一のスレッドだけを処理し、process.abort()
は使用できませんprocess.chdir()
及び、グループまたはユーザーIDを設定するプロセスメソッドは使用できませんprocess.env
は環境変数に対する読み取り専用の参照となりますprocess.title
は変更できません- 明示的にWorkerサポートでビルドされていないネイティブのアドオンはロードできません
worker.terminate()
が呼び出された結果、実行が停止することが可能性があります- 親プロセスからのIPCチャネルにはアクセスできません
postMessage(value[, transferList])
require('worker').on( 'workerMessage')
を経由し、受信されるメッセージをworkerに送信します。
require('worker').postMessage
と違い、require('worker').on( 'workerMessage')
を経由することに注意して下さい。
require('worker').postMessage
はworker.on('message')
。
terminate([callback])
可能な限り早く、workerスレッド内のすべてのJavaScript実行を止めます。
callbackは終了したら呼び出されます。
threadId
参照されるスレッドIDを返します。
最後に
これらはstabilityが1なのでまだ不安定 + 変更があります。
WorkerはClusterよりもパフォーマンスが良く、利便性があります。
今はAyoだけですが、今後Node側へも入る可能性はあるので注目しておいても良いかもです。