HTML5 supports APIs such as Web Worker, allowing web pages to execute multi-threaded code safely. However, Web Worker is actually subject to many limitations, because it cannot truly share memory data and can only make status notifications through messages, so it cannot even be called "multi-threading" in the true sense.
The interface of Web Worker is very inconvenient to use. It basically comes with a sandbox, runs an independent js file in the sandbox, and communicates with the main thread through postMessage and onMessage:
The results obtained can be found that the id of the data obtained in the thread has increased, but after it is passed back, the id in the bundle of the main thread has not changed. Therefore, the object passed in the thread is actually copied, so If so, the threads do not share data and avoid read and write conflicts, so it is safe. The price of ensuring thread safety is to limit the ability to manipulate main thread objects in the thread.
Such a limited multi-threading mechanism is very inconvenient to use. We certainly hope that Worker can support making the code look like it has the ability to operate multiple threads at the same time. For example, support code that looks like the following:
worker.run(function(bundle){
//do sth in worker thread...
This.runOnUiThread(function(bundle /*shared obj*/){
//do sth in main ui thread...
});
//...
});
In this code, after we start a worker, we can let any code run in the worker, and when we need to operate the ui thread (such as reading and writing DOM), we can return to the main thread for execution through this.runOnUiThread.
So how to implement this mechanism? Look at the code below:
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);
}
The above code defines a ThreadWorker object, which creates a Web Worker that runs thread.js, saves the shared object SharedObj, and processes the messages sent back by thread.js.
If a UI_TASK message is returned from thread.js, then run the function passed by the message, otherwise execute the complete callback of run. Let’s take a look at how thread.js is written:
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});
}
}
}
As you can see, thread.js receives messages from the ui thread, the most important of which is THREAD_TASK, which is the "task" passed by the ui thread that needs to be executed by the worker thread. Since the function is not serializable, What is passed is a string. The worker thread parses the string into a function to execute the task submitted by the main thread (note that the shared object sharedObj is passed in in the task). After the execution is completed, the return result is passed to the ui thread through the message. Let's take a closer look. In addition to the return value returnValue, the shared object sharedObj will also be passed back. When passing back, since the worker thread and the ui thread do not share objects, we artificially synchronize the objects on both sides through assignment (is this thread safe? ? Why? )
You can see that the whole process is not complicated. After this implementation, this ThreadWorker can be used in the following two ways:
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("
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就可以跑起来)。