JavaScript和线程


Answers:


109

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

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


您要搜索的单词是JavaScript Worker线程

除了Gears之外,目前没有任何可用的东西,但是关于如何实现这一点的讨论很多,所以我猜想看这个问题,因为答案无疑会在未来改变。

以下是Gears的相关文档:WorkerPool API

WHATWG有一个针对工作线程的建议草案:Web Workers

还有Mozilla的DOM Worker线程


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

Firefox 3.5具有网络工作者。Web工作者的一些演示,如果您想看到它们的实际效果:

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

Safari 4WebKit每晚都有工作线程:

Chrome已加入Gears,因此可以执行线程操作,尽管它需要用户确认提示(并且它使用与Web Worker不同的API,尽管它可以在安装了Gears插件的任何浏览器中使用):

  • Google Gears WorkerPool演示(这不是一个很好的例子,因为它运行速度太快,无法在Chrome和Firefox中进行测试,尽管IE运行得很慢,以至于它阻止了交互)

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


1
尽管Safari 4支持网络工作者,但似乎只有Firefox支持通过postMessage传递复杂对象:hacks.mozilla.org/2009/07/working-smarter-not-harder请参阅该文章的最后一段,了解Bespin项目中的实际用法的链接,为实现工人API垫片在谷歌齿轮方面添加缺少的功能对工人实施的Safari 4以及它们是如何实现的postMessage的界面的顶部透明的自定义事件的详细信息。
山姆·哈斯勒2009年

6
现在IE9已经发布,您可以将“ IE8只能安装Gears插件执行线程”更新为“ IE8和IE9只能安装Gears插件执行线程”
BenoitParis 2010年

2
@ inf3rno用于在另一个线程上进行冗长的计算,因此它们不会降低浏览器UI的速度。
山姆·哈斯勒

6
@SamHasler您可能想要修改答案。现在,所有现代桌面浏览器都支持Web worker。另请参见caniuse.com/#search=worker
Rob W

2
@SamHasler还值得注意的是,不再支持Google Gears。
skeggse

73

在JavaScript中执行多线程和异步的不同方法

在HTML5之前,JavaScript仅允许每页执行一个线程。

有以模拟与异步执行一些哈克的方式产率setTimeout()setInterval()XMLHttpRequest事件处理程序(看到此信息的用于与例如端部收率setTimeout())。

但是,借助HTML5,我们现在可以使用工作线程来并行执行功能。这是一个使用示例。


真正的多线程

多线程:JavaScript Worker线程

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,每个iframe都会执行一个线程。我们可以通过URL 为iframe提供一些参数,并且iframe可以与其父级进行通信以获取结果并将其打印回(iframe必须位于同一域中)。

此示例不适用于所有浏览器! iframe通常与主页在同一线程/进程中运行(但Firefox和Chromium似乎处理方式有所不同)。

由于该代码段不支持多个HTML文件,因此我将在此处提供不同的代码:

index.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>

thread.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()模拟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>

如您所见,第二种方法非常慢并且冻结了浏览器,因为它使用主线程执行功能。


单线程:使用yield模拟JavaScript并发

Yield ECMAScript 6中的一项新功能,仅适用于Firefox和Chrome的最旧版本(在Chrome中,您需要启用出现在 chrome:// flags /#enable-javascript-harmony中的实验性JavaScript)。

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>


3
这确实是最好的答案。
杰里·刘

14

使用HTML5“附带规范”,不再需要使用setTimeout(),setInterval()等来入侵JavaScript

HTML5&Friends引入了javascript Web Workers规范。它是用于异步且独立地运行脚本的API。

链接到规范教程


11

JavaScript中没有真正的线程。JavaScript是它的可扩展语言,它确实允许您模仿其中的一些。这是我前几天遇到的一个例子


1
“真正的线程”是什么意思?绿色线程是真实线程。
Wes 2013年

10

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

您到底想完成什么?


7

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

现在,我将创建3个线程,这些线程将计算数字加法,可以将数字除以13,然后将数字除以3直到10000000000。这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();

我希望这种方式会有所帮助。


6

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



3

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


1
Google Gears API不再可用。
Ludovic Feltz

3

如果您不能或不想使用任何AJAX东西,请使用iframe或10!;)您可以使进程在与母版页并行的iframe中运行,而不必担心跨浏览器的可比问题或点网AJAX等的语法问题,并且可以从iframe。

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

parent.egFunction();

也可以动态生成iframe,因此,如果需要的话,主要的html代码也可以免费使用。


1
这个描述对我来说太简短了。您能否详细说明如何实现此技术,或者在显示一些代码的教程中发布一些链接?
oligofren

3

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

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

这种方法有点类似于Web Worker,但是您可以让解释器访问浏览器的全局环境。

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

此博客文章中有更详细的解释。


1

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
});

0

使用HTML5 规范,您无需为此编写太多JS或发现一些技巧。

HTML 5中引入的功能之一是Web Workers,它是在后台运行的JavaScript,与其他脚本无关,而不会影响页面的性能。

几乎所有浏览器都支持它:

铬-4.0+

IE-10.0+

Mozilla-3.5+

Safari-4.0以上

歌剧-11.5+

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.