JavaScript và Chủ đề


137

Có cách nào để thực hiện đa luồng trong JavaScript không?


2
JavaScript không chứa các chủ đề, ít nhất là ở dạng hiện tại của nó. Chính xác những gì bạn đang cố gắng để làm?
Eric Pohl

3
Bạn có thể thay đổi câu trả lời được chấp nhận cho câu hỏi này? stackoverflow.com/a/30891727/2576706 Nó được phát triển hơn nhiều cho người dùng trong tương lai ..
Ludovic Feelz

Câu trả lời:


109

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):

  • Google Gears WorkerPool Demo (không phải là một ví dụ hay vì nó chạy quá nhanh để kiểm tra trong Chrome và Firefox, mặc dù IE chạy đủ chậm để thấy nó chặn tương tác)

IE8IE9 chỉ có thể thực hiện các luồng với plugin Gears được cài đặt


1
Mặc dù Safari 4 hỗ trợ người lao động web dường như chỉ Firefox hỗ trợ thông qua đối tượng phức tạp qua postMessage: hacks.mozilla.org/2009/07/working-smarter-not-harder Xem đoạn cuối cùng của bài về cách sử dụng thế giới thực trong dự án Bespin để liên kết đến một shim cho việc triển khai API Công nhân theo Google Gearsbổ sung các tính năng còn thiếu cho trình triển khai Safari 4 của nhân viên và chi tiết về cách họ triển khai các sự kiện tùy chỉnh trong suốt trên giao diện postMessage.
Sam Hasler

6
Bây giờ IE9 đã hết, bạn có thể cập nhật "IE8 chỉ có thể thực hiện các luồng với plugin Gears được cài đặt" thành "IE8 và IE9 chỉ có thể thực hiện các luồng với plugin Gears được cài đặt"
BenoitParis

2
@ inf3rno để thực hiện các tính toán dài trên một luồng khác để chúng không làm chậm giao diện người dùng trình duyệt.
Sam Hasler

6
@SamHasler Bạn có thể muốn xem lại câu trả lời của mình. Công nhân web hiện được hỗ trợ bởi tất cả các trình duyệt máy tính để bàn hiện đại. Xem thêm caniuse.com/#search=worker
Rob W

2
@SamHasler cũng đáng lưu ý rằng Google Gears không còn được hỗ trợ.
skeggse

73

Cách khác nhau để thực hiện đa luồng và không đồng bộ trong JavaScript

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(), XMLHttpRequesthoặ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ấtsetTimeout()).

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.


Đa luồng thực sự

Đa luồng: Chủ đề công nhân JavaScript

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.


Đa luồng: với nhiều iframe

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

Mô phỏng đa luồng

Chuỗi đơn: mô phỏng đồng thời JavaScript với setTimeout ()

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.


Single-thread: mô phỏng đồng thời JavaScript với năng suất

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>


3
Đây thực sự nên là câu trả lời tốt nhất.
Jerry Liu

14

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 .


11

Không có luồng thực sự trong JavaScript. JavaScript là ngôn ngữ dễ uốn, nó cho phép bạn mô phỏng một số ngôn ngữ đó. Đây là một ví dụ tôi đã gặp một ngày khác.


1
Bạn có ý nghĩa gì bởi "luồng thực sự"? Chủ đề màu xanh lá cây là chủ đề thực sự.
Wes

10

Không có đa luồng thực sự trong Javascript, nhưng bạn có thể có hành vi không đồng bộ bằng cách sử dụng setTimeout() và các yêu cầu AJAX không đồng bộ.

Chính xác thì bạn đang cố đạt được điều gì?


7

Đâ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.


6

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.



3

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.


1
API Google Gears không còn khả dụng.
Ludovic Feelz

3

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.


1
Mô tả này là một chút quá ngắn gọn theo ý thích của tôi. Bạn có thể giải thích về cách thực hiện kỹ thuật này hoặc đăng một số liên kết đến một hướng dẫn hiển thị một số mã?
oligofren

3

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 .


1

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

0

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+

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.