Có cách nào để thực hiện đa luồng trong JavaScript không?
Có cách nào để thực hiện đa luồng trong JavaScript không?
Câu trả lời:
Xem http://caniuse.com/#search=worker để biết thông tin hỗ trợ cập nhật nhất.
Sau đây là tình trạng hỗ trợ vào khoảng năm 2009.
Các từ bạn muốn google là Chủ đề Công nhân JavaScript
Ngoài Gears hiện tại không có gì khả dụng, nhưng có rất nhiều thảo luận về cách thực hiện điều này vì vậy tôi đoán hãy xem câu hỏi này vì câu trả lời sẽ không có gì thay đổi trong tương lai.
Đây là tài liệu liên quan cho Gears: WorkerPool API
WHATWG có Dự thảo Đề xuất cho chủ đề công nhân: Công nhân web
Và còn có Chủ đề Công nhân DOM của Mozilla
Cập nhật: Tháng 6 năm 2009, trạng thái hỗ trợ trình duyệt hiện tại cho các luồng JavaScript
Firefox 3.5 có nhân viên web. Một số bản demo của nhân viên web, nếu bạn muốn thấy họ hoạt động:
Plugin Gears cũng có thể được cài đặt trong Firefox.
Safari 4 và các nightlies WebKit có các luồng worker:
Chrome có Gears nướng, do đó, nó có thể thực hiện các luồng, mặc dù nó yêu cầu lời nhắc xác nhận từ người dùng (và nó sử dụng API khác cho nhân viên web, mặc dù nó sẽ hoạt động trong bất kỳ trình duyệt nào có cài đặt plugin Gears):
IE8 và IE9 chỉ có thể thực hiện các luồng với plugin Gears được cài đặt
Trước HTML5 JavaScript chỉ cho phép thực thi một luồng trên mỗi trang.
Có một số cách hacky để mô phỏng một thực hiện không đồng bộ với năng suất , setTimeout()
, setInterval()
, XMLHttpRequest
hoặc xử lý sự kiện (xem phần cuối của bài viết này cho một ví dụ với năng suất và setTimeout()
).
Nhưng với HTML5, giờ đây chúng ta có thể sử dụng Chủ đề Công nhân để song song thực hiện các chức năng. Dưới đây là một ví dụ về việc sử dụng.
HTML5 đã giới thiệu Chủ đề Công nhân Web (xem: tính tương thích của trình duyệt )
Lưu ý: IE9 và các phiên bản trước đó không hỗ trợ.
Các luồng công nhân này là các luồng JavaScript chạy trong nền mà không ảnh hưởng đến hiệu suất của trang. Để biết thêm thông tin về Web Worker, hãy đọc tài liệu hoặc hướng dẫn này .
Dưới đây là một ví dụ đơn giản với 3 luồng Công nhân web được tính đến MAX_VALUE và hiển thị giá trị được tính hiện tại trong trang của chúng tôi:
//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>
Chúng ta có thể thấy rằng ba luồng được thực thi đồng thời và in giá trị hiện tại của chúng trong trang. Họ không đóng băng trang vì chúng được thực thi trong nền với các luồng riêng biệt.
Một cách khác để đạt được điều này là sử dụng nhiều iframe , mỗi cái sẽ thực thi một luồng. Chúng tôi có thể cung cấp cho iframe một số tham số bằng URL và iframe có thể giao tiếp với cha mẹ của anh ấy để lấy kết quả và in lại ( iframe phải nằm trong cùng một tên miền).
Ví dụ này không hoạt động trong tất cả các trình duyệt! iframes thường chạy trong cùng một luồng / tiến trình như trang chính (nhưng Firefox và Chromium dường như xử lý nó khác nhau).
Vì đoạn mã không hỗ trợ nhiều tệp HTML, tôi sẽ chỉ cung cấp các mã khác nhau ở đây:
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>
chủ đề.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);
}
})();
Cách 'ngây thơ' sẽ là thực thi chức năng setTimeout()
lần lượt như thế này:
setTimeout(function(){ /* Some tasks */ }, 0);
setTimeout(function(){ /* Some tasks */ }, 0);
[...]
Nhưng phương pháp này không hiệu quả vì mỗi tác vụ sẽ được thực hiện lần lượt.
Chúng ta có thể mô phỏng thực thi không đồng bộ bằng cách gọi hàm đệ quy như thế này:
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>
Như bạn có thể thấy phương thức thứ hai này rất chậm và đóng băng trình duyệt vì nó sử dụng luồng chính để thực thi các chức năng.
Yield là một tính năng mới trong ECMAScript 6 , nó chỉ hoạt động trên phiên bản Firefox và Chrome cũ nhất (trong Chrome bạn cần kích hoạt JavaScript thử nghiệm xuất hiện trong chrome: // flags / # enable-javascript-hòa hợp ).
Từ khóa năng suất làm cho việc thực thi chức năng của trình tạo tạm dừng và giá trị của biểu thức theo từ khóa năng suất được trả về cho người gọi của trình tạo. Nó có thể được coi là một phiên bản dựa trên trình tạo của từ khóa return.
Một trình tạo cho phép bạn tạm dừng thực thi một chức năng và tiếp tục lại nó sau. Một máy phát điện có thể được sử dụng để lên lịch cho các chức năng của bạn với một kỹ thuật gọi là trampolining .
Dưới đây là ví dụ:
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>
Với "thông số phụ" HTML5, không cần phải hack javascript nữa với setTimeout (), setInterval (), v.v.
HTML5 & Friends giới thiệu Công nhân web javascript đặc tả . Nó là một API để chạy các kịch bản không đồng bộ và độc lập.
Liên kết đến các đặc điểm kỹ thuật và một hướng dẫn .
Đây chỉ là một cách để mô phỏng đa luồng trong Javascript
Bây giờ tôi sẽ tạo 3 luồng sẽ tính toán thêm số, số có thể được chia cho 13 và số có thể được chia từ 3 đến 10000000000. Và 3 hàm này không thể chạy cùng lúc với ý nghĩa của Đồng thời. Nhưng tôi sẽ chỉ cho bạn một mẹo giúp các hàm này chạy đệ quy cùng một lúc: jsFiddle
Mã này thuộc về tôi.
Bộ phận cơ thể
<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>
Phần 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();
Tôi hy vọng cách này sẽ hữu ích.
Bạn có thể sử dụng Narrative JavaScript , trình biên dịch sẽ chuyển đổi mã của bạn thành một máy trạng thái, cho phép bạn mô phỏng luồng một cách hiệu quả. Nó làm như vậy bằng cách thêm một toán tử "năng suất" (ký hiệu là '->') vào ngôn ngữ cho phép bạn viết mã không đồng bộ trong một khối mã tuyến tính duy nhất.
Trong Javascript thô, cách tốt nhất bạn có thể làm là sử dụng một vài cuộc gọi không đồng bộ (xmlhttprequest), nhưng điều đó không thực sự phân luồng và rất hạn chế. Google Gears thêm một số API vào trình duyệt, một số API có thể được sử dụng để hỗ trợ luồng.
Nếu bạn không thể hoặc không muốn sử dụng bất kỳ nội dung AJAX nào, hãy sử dụng iframe hoặc ten! ;) Bạn có thể có các quy trình chạy song song với trang chính mà không phải lo lắng về các sự cố có thể so sánh với trình duyệt chéo hoặc các vấn đề cú pháp với chấm mạng AJAX, v.v. và bạn có thể gọi JavaScript của trang chính (bao gồm cả JavaScript mà nó đã nhập) iframe.
Ví dụ: trong iframe cha, để gọi egFunction()
trong tài liệu gốc sau khi nội dung iframe đã được tải (đó là phần không đồng bộ)
parent.egFunction();
Tự động tạo iframe cũng vì vậy mã html chính không có chúng nếu bạn muốn.
Một phương pháp khả thi khác là sử dụng trình thông dịch javascript trong môi trường javascript.
Bằng cách tạo nhiều trình thông dịch và kiểm soát thực thi của chúng từ luồng chính, bạn có thể mô phỏng đa luồng với mỗi luồng chạy trong môi trường riêng của nó.
Cách tiếp cận có phần giống với nhân viên web, nhưng bạn cung cấp cho người phiên dịch quyền truy cập vào môi trường toàn cầu của trình duyệt.
Tôi đã thực hiện một dự án nhỏ để chứng minh điều này .
Một lời giải thích chi tiết hơn trong bài viết trên blog này .
Javascript không có chủ đề, nhưng chúng tôi có nhân viên.
Công nhân có thể là một lựa chọn tốt nếu bạn không cần các đối tượng chia sẻ.
Hầu hết các triển khai trình duyệt sẽ thực sự phân tán nhân viên trên tất cả các lõi cho phép bạn sử dụng tất cả các lõi. Bạn có thể xem bản demo của cái này ở đây .
Tôi đã phát triển một thư viện gọi là task.js , điều này rất dễ thực hiện.
task.js Giao diện đơn giản hóa để có được mã chuyên sâu CPU để chạy trên tất cả các lõi (node.js và web)
Một ví dụ sẽ là
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
});
Với đặc tả HTML5 bạn không cần phải viết quá nhiều JS cho cùng hoặc tìm một số hack.
Một trong những tính năng được giới thiệu trong HTML5 là Web Workers là JavaScript chạy trong nền, độc lập với các tập lệnh khác, mà không ảnh hưởng đến hiệu suất của trang.
Nó được hỗ trợ trong hầu hết các trình duyệt:
Chrome - 4.0+
IE - 10.0+
Mozilla - 3.5+
Safari - 4.0+
Opera - 11,5+