JavaScript 和 Threads在 JavaScript 中执行多线程和异步的不同方法真正的多线程模拟多线程
有没有办法在JavaScript中做多线程?
有没有办法在JavaScript中做多线程?
有关最新的支持信息,请参阅 http://caniuse.com/#search=worker。
以下是2009年左右的支持状态。
你想谷歌的单词是JavaScript Worker Threads
除了Gears之外,现在没有任何可用的信息,但是有很多关于如何实现这一点的讨论,所以我想看看这个问题,因为答案无疑会在未来发生变化。
以下是 Gears: WorkerPool API 的相关文档
WHATWG 有一个针对工作线程的建议草案:Web 工作者
还有Mozilla的DOM Worker Threads。
更新:2009 年 6 月,浏览器对 JavaScript 线程的支持现状
Firefox 3.5 有 Web worker。一些 Web 工作者的演示,如果你想看到他们的实际应用:
Gears插件也可以安装在Firefox中。
Safari 4 和 WebKit nightlies 都有工作线程:
Chrome内置了Gears,因此它可以做线程,尽管它需要用户的确认提示(并且它使用与Web工作者不同的API,尽管它可以在任何安装了Gears插件的浏览器中工作):
IE8 和 IE9 只能在安装了 Gears 插件的情况下进行线程
在HTML5之前,JavaScript只允许每页执行一个线程。
有一些黑客方法可以使用 Yield、、 或事件处理程序模拟异步执行(有关 yield 和 ) 的示例,请参阅本文的结尾。setTimeout()
setInterval()
XMLHttpRequest
setTimeout()
但是有了HTML5,我们现在可以使用工作线程来并行执行函数。下面是一个使用示例。
HTML5 引入了 Web Worker Threads(参见:浏览器兼容性)
注意:IE9 及更早版本不支持它。
这些工作线程是在后台运行的 JavaScript 线程,不会影响页面的性能。有关 Web Worker 的详细信息,请阅读文档或本教程。
下面是一个简单的示例,其中包含 3 个 Web Worker 线程,这些线程计数为MAX_VALUE并在我们的页面中显示当前计算值:
//As a worker normally take another JavaScript file to execute we convert the function in an URL: http://stackoverflow.com/a/16799132/2576706
function getScriptPath(foo){ return window.URL.createObjectURL(new Blob([foo.toString().match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1]],{type:'text/javascript'})); }
var MAX_VALUE = 10000;
/*
* Here are the workers
*/
//Worker 1
var worker1 = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = 0;
while(value <= e.data){
self.postMessage(value);
value++;
}
}, false);
}));
//We add a listener to the worker to get the response and show it in the page
worker1.addEventListener('message', function(e) {
document.getElementById("result1").innerHTML = e.data;
}, false);
//Worker 2
var worker2 = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = 0;
while(value <= e.data){
self.postMessage(value);
value++;
}
}, false);
}));
worker2.addEventListener('message', function(e) {
document.getElementById("result2").innerHTML = e.data;
}, false);
//Worker 3
var worker3 = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = 0;
while(value <= e.data){
self.postMessage(value);
value++;
}
}, false);
}));
worker3.addEventListener('message', function(e) {
document.getElementById("result3").innerHTML = e.data;
}, false);
// Start and send data to our worker.
worker1.postMessage(MAX_VALUE);
worker2.postMessage(MAX_VALUE);
worker3.postMessage(MAX_VALUE);
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
我们可以看到这三个线程以并发方式执行,并在页面中打印其当前值。它们不会冻结页面,因为它们是在后台使用单独的线程执行的。
实现这一目标的另一种方法是使用多个iframe,每个iframe将执行一个线程。我们可以通过URL为iframe提供一些参数,iframe可以与他的父级进行通信,以便获得结果并将其打印回来(iframe必须位于同一域中)。
此示例并非在所有浏览器中都有效!iframe通常与主页在同一线程/进程中运行(但Firefox和Chromium似乎以不同的方式处理它)。
由于代码片段不支持多个HTML文件,因此我将在此处提供不同的代码:
索引.html:
//The 3 iframes containing the code (take the thread id in param)
<iframe id="threadFrame1" src="thread.html?id=1"></iframe>
<iframe id="threadFrame2" src="thread.html?id=2"></iframe>
<iframe id="threadFrame3" src="thread.html?id=3"></iframe>
//Divs that shows the result
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
<script>
//This function is called by each iframe
function threadResult(threadId, result) {
document.getElementById("result" + threadId).innerHTML = result;
}
</script>
线程.html:
//Get the parameters in the URL: http://stackoverflow.com/a/1099670/2576706
function getQueryParams(paramName) {
var qs = document.location.search.split('+').join(' ');
var params = {}, tokens, re = /[?&]?([^=]+)=([^&]*)/g;
while (tokens = re.exec(qs)) {
params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
}
return params[paramName];
}
//The thread code (get the id from the URL, we can pass other parameters as needed)
var MAX_VALUE = 100000;
(function thread() {
var threadId = getQueryParams('id');
for(var i=0; i<MAX_VALUE; i++){
parent.threadResult(threadId, i);
}
})();
“幼稚”的方法是像这样一个接一个地执行函数:setTimeout()
setTimeout(function(){ /* Some tasks */ }, 0);
setTimeout(function(){ /* Some tasks */ }, 0);
[...]
但此方法不起作用,因为每个任务都将一个接一个地执行。
我们可以通过递归调用函数来模拟异步执行,如下所示:
var MAX_VALUE = 10000;
function thread1(value, maxValue){
var me = this;
document.getElementById("result1").innerHTML = value;
value++;
//Continue execution
if(value<=maxValue)
setTimeout(function () { me.thread1(value, maxValue); }, 0);
}
function thread2(value, maxValue){
var me = this;
document.getElementById("result2").innerHTML = value;
value++;
if(value<=maxValue)
setTimeout(function () { me.thread2(value, maxValue); }, 0);
}
function thread3(value, maxValue){
var me = this;
document.getElementById("result3").innerHTML = value;
value++;
if(value<=maxValue)
setTimeout(function () { me.thread3(value, maxValue); }, 0);
}
thread1(0, MAX_VALUE);
thread2(0, MAX_VALUE);
thread3(0, MAX_VALUE);
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
如您所见,第二种方法非常慢,并且会冻结浏览器,因为它使用主线程来执行函数。
Yield 是 ECMAScript 6 中的一项新功能,它仅适用于最旧版本的 Firefox 和 Chrome(在 Chrome 中,您需要启用实验性 JavaScript,chrome://flags/#enable-javascript-harmony)。
yield 关键字会导致生成器函数执行暂停,yield 关键字后面的表达式值将返回给生成器的调用方。可以将其视为基于生成器的 return 关键字版本。
生成器允许您暂停函数的执行,并在以后恢复它。生成器可用于通过一种称为蹦床的技术来调度函数。
下面是一个示例:
var MAX_VALUE = 10000;
Scheduler = {
_tasks: [],
add: function(func){
this._tasks.push(func);
},
start: function(){
var tasks = this._tasks;
var length = tasks.length;
while(length>0){
for(var i=0; i<length; i++){
var res = tasks[i].next();
if(res.done){
tasks.splice(i, 1);
length--;
i--;
}
}
}
}
}
function* updateUI(threadID, maxValue) {
var value = 0;
while(value<=maxValue){
yield document.getElementById("result" + threadID).innerHTML = value;
value++;
}
}
Scheduler.add(updateUI(1, MAX_VALUE));
Scheduler.add(updateUI(2, MAX_VALUE));
Scheduler.add(updateUI(3, MAX_VALUE));
Scheduler.start()
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>