有没有办法在JavaScript中进行多线程处理?
在JavaScript中执行多线程和异步的不同方法
在HTML5之前,JavaScript仅允许每页执行一个线程。
有以模拟与异步执行一些哈克的方式产率,setTimeout(),setInterval(),XMLHttpRequest或事件处理程序(看到此信息的用于与例如端部收率和setTimeout())。
setTimeout()
setInterval()
XMLHttpRequest
但是,借助HTML5,我们现在可以使用工作线程来并行执行功能。这是一个使用示例。
真正的多线程
多线程:JavaScript Worker线程
注意:IE9和更早版本不支持它。
这是一个简单的示例,其中包含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()
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);
如您所见,第二种方法非常慢并且会冻结浏览器,因为它使用主线程来执行功能。
单线程:使用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>