JavaScript 和线程

有没有什么方法可以在 JavaScript 中实现多线程?

163423 次浏览

在原始的 Javascript 中,您能做的最好的事情就是使用少量的异步调用(xmlhttprerequest) ,但是这并不是真正的线程化,而且非常有限。Google Gears向浏览器添加了许多 API,其中一些可用于线程支持。

JavaScript 中没有真正的线程。JavaScript 作为一种具有可塑性的语言,确实允许您模拟其中的一些内容。这是我前几天偶然发现的 例子

有关最新的支持信息,请参见 http://caniuse.com/#search=worker

以下是大约2009年的支持状况。


你要谷歌的单词是 JavaScript 工作线程

除了 齿轮之外,目前还没有什么可用的东西,但是关于如何实现这一点有很多讨论,所以我想看看这个问题,因为答案无疑将在未来发生变化。

这是 Gears 的相关文档: WorkerPool API

WHATWG 有一个针对工作线程的建议草案: 网络工作者

还有 Mozilla 的 DOM 工作线程


更新: 2009年6月,JavaScript 线程浏览器支持的当前状态

Firefox3.5 有 web worker,一些 web worker 的演示,如果你想看到他们的行动:

Gears 插件也可以安装在 Firefox 中。

Safari 4 WebKit 夜间新闻都有工作线程:

Chrome 内置了 Gears,所以它可以做线程,尽管它需要用户的确认提示(并且它对 web 工作者使用不同的 API,尽管它可以在安装了 Gears 插件的任何浏览器中工作) :

  • Google Gears WorkerPool Demo (不是一个很好的例子,因为它运行速度太快,无法在 Chrome 和 Firefox 中进行测试,尽管 IE 运行速度足够慢,可以看到它阻碍了交互)

IE8 IE9只能在安装了 Gears 插件的情况下执行线程

Javascript 中没有真正的多线程,但是可以使用 setTimeout()和异步 AJAX 请求获得异步行为。

你到底想干什么?

您可以使用 叙述性 JavaScript,这是一种将代码转换为状态机的编译器,有效地允许您模拟线程。它是通过在语言中添加一个“屈服”运算符(注释为“->”)来实现的,该语言允许您在单个线性代码块中编写异步代码。

应该出来今天支持的新的 v8引擎(我认为)

使用带有 setTimeout ()、 setInterval ()等的 HTML5“ side-specs”不再需要黑进 javascript 了

HTML5 & Friends 引入了 javascript 网络工作者规范,它是一个用于异步和独立运行脚本的 API。

链接到 规格教程

如果你不能或不想使用任何 AJAX 的东西,使用一个 iframe 或10!你可以让进程与主页并行运行,而不用担心跨浏览器的类似问题或者.net AJAX 等语法问题,你还可以从一个 iframe 调用主页的 JavaScript (包括它导入的 JavaScript)。

例如,在父 iframe 中,一旦加载了 iframe 内容(这是异步部分) ,就调用父文档中的 egFunction()

parent.egFunction();

也可以动态生成 iframe,这样如果需要的话,主 html 代码就不会受到它们的影响。

这里只是一种在 Javascript 中实现 模拟多线程的方法

现在我要创建3个线程来计算数字加法,数字可以用13除,数字可以用3除到1000000000。而且这3个函数不能与并发意味着的同时运行。但是我将向您展示一个让这些函数在同一时间递归运行的技巧: JsFiddle

这密码是我的。

身体部位

    <div class="div1">
<input type="button" value="start/stop" onclick="_thread1.control ? _thread1.stop() : _thread1.start();" /><span>Counting summation of numbers till 10000000000</span> = <span id="1">0</span>
</div>
<div class="div2">
<input type="button" value="start/stop" onclick="_thread2.control ? _thread2.stop() : _thread2.start();" /><span>Counting numbers can be divided with 13 till 10000000000</span> = <span id="2">0</span>
</div>
<div class="div3">
<input type="button" value="start/stop" onclick="_thread3.control ? _thread3.stop() : _thread3.start();" /><span>Counting numbers can be divided with 3 till 10000000000</span> = <span id="3">0</span>
</div>

Javascript 部分

var _thread1 = {//This is my thread as object
control: false,//this is my control that will be used for start stop
value: 0, //stores my result
current: 0, //stores current number
func: function () {   //this is my func that will run
if (this.control) {      // checking for control to run
if (this.current < 10000000000) {
this.value += this.current;
document.getElementById("1").innerHTML = this.value;
this.current++;
}
}
setTimeout(function () {  // And here is the trick! setTimeout is a king that will help us simulate threading in javascript
_thread1.func();    //You cannot use this.func() just try to call with your object name
}, 0);
},
start: function () {
this.control = true;   //start function
},
stop: function () {
this.control = false;    //stop function
},
init: function () {
setTimeout(function () {
_thread1.func();    // the first call of our thread
}, 0)
}
};
var _thread2 = {
control: false,
value: 0,
current: 0,
func: function () {
if (this.control) {
if (this.current % 13 == 0) {
this.value++;
}
this.current++;
document.getElementById("2").innerHTML = this.value;
}
setTimeout(function () {
_thread2.func();
}, 0);
},
start: function () {
this.control = true;
},
stop: function () {
this.control = false;
},
init: function () {
setTimeout(function () {
_thread2.func();
}, 0)
}
};
var _thread3 = {
control: false,
value: 0,
current: 0,
func: function () {
if (this.control) {
if (this.current % 3 == 0) {
this.value++;
}
this.current++;
document.getElementById("3").innerHTML = this.value;
}
setTimeout(function () {
_thread3.func();
}, 0);
},
start: function () {
this.control = true;
},
stop: function () {
this.control = false;
},
init: function () {
setTimeout(function () {
_thread3.func();
}, 0)
}
};


_thread1.init();
_thread2.init();
_thread3.init();

我希望这个方法能有所帮助。

另一种可能的方法是在 javascript 环境中使用 javascript 解释器。

通过创建多个解释器并从主线程控制它们的执行,可以模拟多线程,每个线程在自己的环境中运行。

这种方法有点类似于 web worker,但是您允许解释器访问浏览器的全局环境。

我对 证明这一点做了一个小项目。

这篇博文中有更详细的解释。

在 JavaScript 中实现多线程和异步的不同方法

在 HTML5JavaScript 之前,每个页面只允许执行一个线程。

有一些古怪的方法可以用 投降吧setTimeout()setInterval()XMLHttpRequest事件处理程序来模拟异步执行(有关 投降setTimeout()的例子,请参阅本文末尾)。

但是通过 HTML5,我们现在可以使用 WorkerThreads 来并行执行函数。下面是一个使用的例子。


真正的多线程

多线程: JavaScript 工作线程

HTML5 引入了 Web Worker Threads (参见: 浏览器兼容性)
注意: IE9和早期版本不支持。

这些工作线程是在后台运行而不影响页面性能的 JavaScript 线程。有关 网络工作者 阅读文件本教程的详细信息,请参阅。

下面是一个简单的例子,有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

另一种实现方法是使用多个 Iframes,每个 Iframes将执行一个线程。我们可以通过 URL 给 Iframe一些参数,而 Iframe可以与他的父级进行通信,以获得结果并打印回来(Iframe必须在同一个域中)。

这个示例并不适用于所有浏览器!Iframes通常与主页在同一个线程/进程中运行(但 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>

返回文章页面

//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 ()模拟 JavaScript 并发

“幼稚”的方法是像下面这样一个接一个地执行函数 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>

正如您所看到的,第二个方法非常慢,并且会冻结浏览器,因为它使用主线程来执行函数。


单线程: 使用屈服值模拟 JavaScript 并发

产量 ECMAScript 6的一个新特性,它只能在最老版本的 Firefox 和 Chrome 上运行(在 Chrome 中,你需要在 Chrome://Flag/# able-javascript-Harmony中启用 实验性 JavaScript)。

屈服关键字导致生成器函数执行暂停,并将屈服关键字后面的表达式的值返回给生成器的调用方。它可以被看作是 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>

Javascript 没有线程,但是我们有工作线程。

如果您不需要共享对象,那么工作器可能是一个很好的选择。

大多数浏览器实现实际上将工作线分散到所有核心,允许您利用所有核心。您可以看到这个 给你的演示。

我已经开发了一个名为 Task.js的库,它使得这项工作非常容易。

Task.js 简化接口,让 CPU 密集型代码在所有核心(node.js 和 web)上运行

举个例子

function blocking (exampleArgument) {
// block thread
}


// turn blocking pure function into a worker task
const blockingAsync = task.wrap(blocking);


// run task on a autoscaling worker pool
blockingAsync('exampleArgumentValue').then(result => {
// do something with result
});

使用 HTML5规格,您不需要为同样的内容编写太多的 JS,也不需要找到一些技巧。

HTML5中引入的特性之一是 网络工作者,它是在后台运行的 JavaScript,独立于其他脚本,而不会影响页面的性能。

几乎所有浏览器都支持:

Chrome-4.0 +

IE-10.0 +

Mozilla-3.5 +

Safari-4.0 +

Opera-11.5 +

Topaz 是用于.NET: https://github.com/koculu/topaz的无锁多线程 Javascript 引擎