HTML5 supports APIs like Web Worker, allowing web pages to execute multi-threaded code in a safe situation. However, Web Worker is actually subject to many restrictions because it cannot truly share memory data and can only use messages to inform state, 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, which runs an independent js file in the sandbox, and communicates with the main thread through postMessage and onMessge:
The code copy is as follows:
var worker = new Worker("my.js");
var bundle = {message:'Hello world', id:1};
worker.postMessage(bundle); //postMessage can pass a serializable object in the past
worker.onmessage = function(evt){
console.log(evt.data); //Compare objects passed back in the worker with objects in the main thread
console.log(bundle); //{message:'Hello world', id:1}
}
The code copy is as follows:
//in my.js
onmessage = function(evt){
var data = evt.data;
data.id++;
postMessage(data); //{message:'Hello world', id:2}
}
The result 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 is not changed. Therefore, the object passed in the thread is actually copied. In this way, the thread does not share the data, avoiding read and write conflicts, so it is safe. The cost of ensuring thread safety is to limit the ability to operate main thread objects in the thread.
Such a limited multi-threading mechanism is inconvenient to use. Of course, we hope that Worker can support the ability to make the code appear to operate multi-threading at the same time. For example, support code that looks like the following:
The code copy is as follows:
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...
});
//...
});
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 the dom), we can return to the main thread to execute through this.runOnUiThread.
So how to implement this mechanism? Look at the following code:
The code copy is as follows:
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);
}
The above code defines a ThreadWorker object. This object creates a Web Worker running thread.js, saves the shared object SharedObj, and processes the messages sent back by thread.js.
If a UI_TASK message is transmitted back in thread.js, then run the function passed by this message. Otherwise, execute the complete callback of run, let's see how thread.js is written:
The code copy is as follows:
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});
}
}
}
It can be seen that thread.js receives messages from ui threads, the most important of which is THREAD_TASK, which is the "task" passed by ui threads that need to be executed by the worker thread. Since function is not serializable, it is passed a string. The worker thread parses the string into a function to execute the tasks submitted by the main thread (note that the shared object sharedObj is passed in the task). After the execution is completed, the return result will be passed to the ui thread through message. Let's take a closer look at the shared object sharedObj in addition to the return value returnValue. When passing back, since the worker thread and the ui thread do not share the object, 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 have the following two uses:
The code copy is as follows:
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("<li>"+sharedObj.i+"</li>");
});
this.sleep(500);
}
return sharedObj.i;
}, function(r){
console.log("t2>" + r.returnValue + ":" + r.error);
});
This usage gives the code good structure, flexibility and maintainability in both form and semantic terms.
Okay, that's all for the discussion on the usage of Web Worker. Interested students can take a look at this project: https://github.com/akira-cn/WorkerThread.js (Since Worker needs to use server testing, I specially put a copycat httpd.js in the project, which is a very simple http service js, and you can run it with node directly).