JavaScriptにおけるWeb workerマルチスレッドAPI研究
Web Workerのインタフェースは使いにくいです.基本的にsandboxを持っていて、砂箱の中で独立したjsファイルを走って、postMessageとonMessageを通じてメインスレッドと通信します.
var worker = new Worker("my.js");
var bundle = {message:'Hello world', id:1};
worker.postMessage(bundle); //postMessage
worker.onmessage = function(evt){
console.log(evt.data); // worker
console.log(bundle); //{message:'Hello world', id:1}
}
//in my.js
onmessage = function(evt){
var data = evt.data;
data.id++;
postMessage(data); //{message:'Hello world', id:2}
}
得られた結果,スレッドで得られたdataのidは増加したが,伝達された後,メインスレッドのbundleでのidは変化しなかったため,スレッドで伝達されたオブジェクトは実際にcopy 1部となっており,これによりスレッドはデータを共有せず,読み書き衝突を回避したので安全である.スレッドのセキュリティを保証する代価は、スレッド内でプライマリ・スレッド・オブジェクトを操作する能力を制限することです.
このような限られたマルチスレッドメカニズムを使用するのは不便です.もちろん、Workerは、コードがマルチスレッドを同時に操作するように見える能力をサポートすることを望んでいます.例えば、次のように見えるコードをサポートします.
var worker = new ThreadWorker(bundle /*shared obj*/);
worker.run(function(bundle){
//do sth in worker thread...
this.runOnUiThread(function(bundle /*shared obj*/){
//do sth in main ui thread...
});
//...
});
このコードでは、workerを起動すると、任意のコードをworkerに走らせることができ、uiスレッド(読み書きdomなど)を操作する必要がある場合はthisを使用することができます.runOnUIThreadはメインスレッドに戻って実行します.
では、このメカニズムをどのように実現するのでしょうか.次のコードを見てください.
function WorkerThread(sharedObj){
this._worker = new Worker("thread.js");
this._completes = {};
this._task_id = 0;
this.sharedObj = sharedObj;
var self = this;
this._worker.onmessage = function(evt){
var ret = evt.data;
if(ret.__UI_TASK__){
//run on ui task
var fn = (new Function("return "+ret.__UI_TASK__))();
fn(ret.sharedObj);
}else{
self.sharedObj = ret.sharedObj;
self._completes[ret.taskId](ret);
}
}
}
WorkerThread.prototype.run = function(task, complete){
var _task = {__THREAD_TASK__:task.toString(), sharedObj: this.sharedObj, taskId: this._task_id};
this._completes[this._task_id++] = complete;
this._worker.postMessage(_task);
}
このコードは、threadWorkerを実行するオブジェクトを作成するThreadWorkerオブジェクトを定義しています.jsのWeb Workerは、共有オブジェクトSharedObjを保存し、thread.jsから返されたメッセージを処理します.
もしそうならjsでUIが返されましたTASKメッセージ、このメッセージが伝わってきたfunctionを実行します.そうしないとrunのcompleteコールバックを実行します.threadを見てみましょう.jsはどのように書きましたか.
onmessage = function(evt){
var data = evt.data;
if(data && data.__THREAD_TASK__){
var task = data.__THREAD_TASK__;
try{
var fn = (new Function("return "+task))();
var ctx = {
threadSignal: true,
sleep: function(interval){
ctx.threadSignal = false;
setTimeout(_run, interval);
},
runOnUiThread: function(task){
postMessage({__UI_TASK__:task.toString(), sharedObj:data.sharedObj});
}
}
function _run(){
ctx.threadSignal = true;
var ret = fn.call(ctx, data.sharedObj);
postMessage({error:null, returnValue:ret, __THREAD_TASK__:task, sharedObj:data.sharedObj, taskId: data.taskId});
}
_run(0);
}catch(ex){
postMessage({error:ex.toString() , returnValue:null, sharedObj: data.sharedObj});
}
}
}
見えるよjsはuiスレッドから送信されたメッセージを受信し、その中で最も重要なのはTHREAD_である.TASK、これはuiスレッドから送られてきたworkerスレッドが実行しなければならない「タスク」であり、functionはシーケンス化できないため文字列が渡され、workerスレッドは文字列をfunctionに解析することによってメインスレッドがコミットしたタスクを実行し(タスクでは共有対象sharedObjが送られることに注意)、実行が完了すると戻り結果がmessageを通じてuiスレッドに渡される.戻り値returnValueのほか、共有オブジェクトsharedObjも返されます.返されるとき、workerスレッドとuiスレッドはオブジェクトを共有しないため、両辺のオブジェクトを値付けで同期します(スレッドは安全ですか?なぜですか?)
プロセス全体が複雑ではないことがわかります.このように実現すると、このThreadWorkerには次の2つの使い方があります.
var t1 = new WorkerThread({i: 100} /*shared obj*/);
setInterval(function(){
t1.run(function(sharedObj){
return sharedObj.i++;
},
function(r){
console.log("t1>" + r.returnValue + ":" + r.error);
}
);
}, 500);
var t2 = new WorkerThread({i: 50});
t2.run(function(sharedObj){
while(this.threadSignal){
sharedObj.i++;
this.runOnUiThread(function(sharedObj){
W("body ul").appendChild("
"+sharedObj.i+" ");
});
this.sleep(500);
}
return sharedObj.i;
}, function(r){
console.log("t2>" + r.returnValue + ":" + r.error);
});
このような使い方は、形式的にも意味的にもコードに良好な構造、柔軟性、メンテナンス性をもたらします.
では、Web Workerの使い方について検討して紹介します.興味のある人はこのプロジェクトを見てみましょう.https://github.com/akira-cn/WorkerThread.js(Workerはサーバーでテストする必要があるので、わざわざプロジェクトにパクリのhttpd.jsを入れました.とても粗末なhttpサービスのjsで、nodeで走ることができます).