JavaScript และเธรด


140

มีวิธีทำมัลติเธรดใน JavaScript หรือไม่?


2
JavaScript ไม่มีเธรดอย่างน้อยก็อยู่ในรูปแบบปัจจุบัน คุณกำลังพยายามทำอะไรกันแน่?
Eric Pohl

3
คุณสามารถเปลี่ยนคำตอบที่ยอมรับเป็นคำตอบนี้ได้หรือไม่? stackoverflow.com/a/30891727/2576706มีการพัฒนามากขึ้นสำหรับผู้ใช้ในอนาคต ..
Ludovic Feltz

คำตอบ:


110

โปรดดูที่http://caniuse.com/#search=workerสำหรับข้อมูลการสนับสนุนล่าสุด

ต่อไปนี้คือสถานะของการสนับสนุนประมาณปี 2009


คำที่คุณต้องการใช้กับ Google คือJavaScript Worker Threads

นอกเหนือจากGearsแล้วตอนนี้ยังไม่มีอะไรให้บริการ แต่มีการพูดคุยมากมายเกี่ยวกับวิธีการใช้งานดังนั้นฉันเดาว่าดูคำถามนี้เพราะคำตอบจะไม่มีการเปลี่ยนแปลงในอนาคตอย่างไม่ต้องสงสัย

นี่คือเอกสารที่เกี่ยวข้องสำหรับ Gears: WorkerPool API

WHATWG มีคำแนะนำแบบร่างสำหรับเธรดผู้ปฏิบัติงาน: Web Workers

นอกจากนี้ยังมีDOM Worker Threadsของ Mozilla


อัปเดต:มิถุนายน 2009 สถานะปัจจุบันของเบราว์เซอร์รองรับเธรด JavaScript

Firefox 3.5มีพนักงานเว็บ การสาธิตบางส่วนของผู้ปฏิบัติงานบนเว็บหากคุณต้องการดูการใช้งานจริง:

นอกจากนี้ยังสามารถติดตั้งปลั๊กอิน Gears ใน Firefox

Safari 4และชุดราตรี WebKitมีเธรดของผู้ปฏิบัติงาน:

Chromeมี Gears อยู่ในตัวดังนั้นจึงสามารถทำเธรดได้แม้ว่าจะต้องใช้ข้อความแจ้งยืนยันจากผู้ใช้ (และใช้ API อื่นสำหรับผู้ปฏิบัติงานบนเว็บแม้ว่าจะทำงานในเบราว์เซอร์ใดก็ได้ที่ติดตั้งปลั๊กอิน Gears):

  • Google Gears WorkerPool Demo (ไม่ใช่ตัวอย่างที่ดีเนื่องจากทำงานเร็วเกินไปที่จะทดสอบใน Chrome และ Firefox แม้ว่า IE จะทำงานช้าพอที่จะเห็นว่าบล็อกการโต้ตอบ)

IE8และIE9สามารถทำเธรดได้เมื่อติดตั้งปลั๊กอิน Gears เท่านั้น


1
แม้ว่า Safari 4 จะรองรับ Web Work แต่ดูเหมือนว่า Firefox เท่านั้นที่รองรับการส่งผ่านวัตถุที่ซับซ้อนผ่าน postMessage: hacks.mozilla.org/2009/07/working-smarter-not-harderดูย่อหน้าสุดท้ายของโพสต์นั้นเกี่ยวกับการใช้งานจริงในโครงการ Bespin สำหรับลิงก์ไปยังshim เพื่อใช้ Worker API ในแง่ของ Google Gearsและเพิ่มคุณสมบัติที่ขาดหายไปในการใช้งาน Safari 4 ของผู้ปฏิบัติงานและรายละเอียดวิธีการนำเหตุการณ์ที่กำหนดเองแบบโปร่งใสมาใช้ที่ด้านบนของอินเทอร์เฟซ postMessage
Sam Hasler

6
ตอนนี้ IE9 ออกแล้วคุณสามารถอัปเดต "IE8 สามารถทำเธรดได้โดยติดตั้งปลั๊กอิน Gears เท่านั้น" เป็น "IE8 และ IE9 สามารถทำได้เฉพาะเธรดที่ติดตั้งปลั๊กอิน Gears เท่านั้น"
BenoitParis

2
@ inf3rno สำหรับการคำนวณที่มีความยาวในเธรดอื่นดังนั้นจึงไม่ทำให้ UI ของเบราว์เซอร์ช้าลง
Sam Hasler

6
@SamHasler คุณอาจต้องการแก้ไขคำตอบของคุณ ขณะนี้ผู้ปฏิบัติงานบนเว็บได้รับการสนับสนุนโดยเบราว์เซอร์เดสก์ท็อปสมัยใหม่ทั้งหมด ดูcaniuse.com/#search=worker
Rob W

2
@SamHasler นอกจากนี้ยังควรสังเกตด้วยว่า Google Gears ไม่ได้รับการสนับสนุนอีกต่อไป
skeggse

73

วิธีที่แตกต่างกันในการทำมัลติเธรดและอะซิงโครนัสใน JavaScript

ก่อน HTML5 JavaScript อนุญาตให้เรียกใช้เธรดเดียวต่อหน้าเท่านั้น

มีบางวิธี hacky เพื่อจำลองการดำเนินการไม่ตรงกันกับเป็นผลผลิต , setTimeout(), setInterval(), XMLHttpRequestหรือจัดการเหตุการณ์ (ดูตอนท้ายของการโพสต์นี้เช่นกับนั้นผลผลิตและsetTimeout())

แต่ด้วย HTML5 ตอนนี้เราสามารถใช้ Worker Threads เพื่อขนานการทำงานของฟังก์ชันได้ นี่คือตัวอย่างการใช้งาน


มัลติเธรดที่แท้จริง

มัลติเธรด: JavaScript Worker Threads

HTML5แนะนำ Web Worker Threads (ดู: ความเข้ากันได้ของเบราว์เซอร์ )
หมายเหตุ: IE9 และเวอร์ชันก่อนหน้าไม่รองรับ

เธรดของผู้ปฏิบัติงานเหล่านี้เป็นเธรด JavaScript ที่ทำงานในพื้นหลังโดยไม่ส่งผลกระทบต่อประสิทธิภาพของเพจ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับWeb Worker โปรดอ่านเอกสารหรือบทช่วยสอนนี้

นี่คือตัวอย่างง่ายๆที่มีเธรด Web Worker 3 เธรดที่นับถึง 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ต้องอยู่ในโดเมนเดียวกัน)

ตัวอย่างนี้ใช้ไม่ได้กับทุกเบราว์เซอร์! โดยปกติiframesจะทำงานในเธรด / กระบวนการเดียวกันกับหน้าหลัก (แต่ดูเหมือนว่า 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);
    }
})();

จำลองมัลติเธรด

เธรดเดี่ยว: จำลองการทำงานพร้อมกันของ 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);
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>

อย่างที่คุณเห็นวิธีที่สองนี้ช้ามากและทำให้เบราว์เซอร์หยุดทำงานเนื่องจากใช้เธรดหลักในการเรียกใช้ฟังก์ชัน


เธรดเดี่ยว: เลียนแบบการทำงานพร้อมกันของ JavaScript กับผลตอบแทน

Yieldเป็นคุณลักษณะใหม่ใน ECMAScript 6ซึ่งใช้งานได้กับ Firefox และ Chrome เวอร์ชันเก่าที่สุดเท่านั้น (ใน Chrome คุณต้องเปิดใช้งาน Experimental JavaScript ที่ปรากฏใน chrome: // flags / # enable-javascript-Harmony )

คีย์เวิร์ดที่ให้ผลตอบแทนทำให้การทำงานของฟังก์ชันเจนเนอเรเตอร์หยุดชั่วคราวและค่าของนิพจน์ที่ตามหลังคีย์เวิร์ด yield จะถูกส่งกลับไปยังผู้เรียกของตัวสร้าง สามารถคิดได้ว่าเป็นเวอร์ชันที่สร้างขึ้นโดยใช้คีย์เวิร์ด return

เครื่องกำเนิดไฟฟ้าช่วยให้คุณสามารถระงับการทำงานของฟังก์ชันและดำเนินการต่อได้ในภายหลัง เครื่องกำเนิดไฟฟ้าสามารถนำมาใช้เพื่อกำหนดตารางการทำงานของคุณด้วยเทคนิคที่เรียกว่าtrampolining

นี่คือตัวอย่าง:

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
นี่น่าจะเป็นคำตอบที่ดีที่สุดจริงๆ
Jerry Liu

14

ด้วย HTML5 "side-spec" ไม่จำเป็นต้องแฮ็ก javascript อีกต่อไปด้วย setTimeout (), setInterval () ฯลฯ

HTML5 & Friends แนะนำจาวาสคริปต์เว็บแรงงานเปค เป็น API สำหรับเรียกใช้สคริปต์แบบอะซิงโครนัสและแยกกัน

เชื่อมโยงไปยังข้อมูลจำเพาะและกวดวิชา


11

ไม่มีเธรดที่แท้จริงใน JavaScript JavaScript เป็นภาษาที่อ่อนได้ซึ่งจะช่วยให้คุณเลียนแบบบางส่วนได้ นี่คือตัวอย่างที่ฉันเจอเมื่อวันก่อน


1
"เธรดจริง" หมายความว่าอย่างไร เธรดสีเขียวคือเธรดที่แท้จริง
Wes

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 หรือสิบ! ;) คุณสามารถมีกระบวนการที่ทำงานใน iframe ควบคู่ไปกับหน้าต้นแบบได้โดยไม่ต้องกังวลเกี่ยวกับปัญหาการเทียบเคียงข้ามเบราว์เซอร์หรือปัญหาทางไวยากรณ์กับ dot net AJAX เป็นต้นและคุณสามารถเรียก JavaScript ของหน้าต้นแบบ (รวมถึง JavaScript ที่นำเข้า) จาก iframe

เช่นใน iframe หลักเพื่อเรียกegFunction()เอกสารพาเรนต์เมื่อโหลดเนื้อหา iframe แล้ว (นั่นคือส่วนอะซิงโครนัส)

parent.egFunction();

สร้าง iframe แบบไดนามิกด้วยดังนั้นโค้ด html หลักจึงเป็นอิสระจากพวกเขาหากคุณต้องการ


1
คำอธิบายนี้สั้นเกินไปสำหรับความชอบของฉัน คุณช่วยอธิบายวิธีการทำเทคนิคนี้อย่างละเอียดหรือโพสต์ลิงก์ไปยังบทช่วยสอนที่แสดงโค้ดบางส่วนได้ไหม
oligofren

3

อีกวิธีหนึ่งที่เป็นไปได้คือการใช้ตัวแปลจาวาสคริปต์ในสภาพแวดล้อมจาวาสคริปต์

ด้วยการสร้างล่ามหลาย ๆ ตัวและควบคุมการดำเนินการจากเธรดหลักคุณสามารถจำลองการทำงานแบบมัลติเธรดโดยแต่ละเธรดทำงานในสภาพแวดล้อมของตนเอง

วิธีการนี้ค่อนข้างคล้ายกับ Web Worker แต่คุณให้ล่ามเข้าถึงสภาพแวดล้อมทั่วโลกของเบราว์เซอร์

ฉันทำโครงการเล็ก ๆ เพื่อสาธิตสิ่งนี้แสดงให้เห็นถึงนี้

คำอธิบายโดยละเอียดเพิ่มเติมในโพสต์บล็อกนี้


1

Javascript ไม่มีเธรด แต่เรามีคนงาน

คนงานอาจเป็นทางเลือกที่ดีหากคุณไม่ต้องการวัตถุที่ใช้ร่วมกัน

การใช้งานเบราว์เซอร์ส่วนใหญ่จะกระจายคนงานไปทั่วทุกคอร์ทำให้คุณสามารถใช้คอร์ทั้งหมดได้ คุณสามารถดูการสาธิตได้ที่นี่ที่นี่

ฉันได้พัฒนาไลบรารีชื่อtask.jsซึ่งทำให้สิ่งนี้ง่ายมาก

task.jsอินเทอร์เฟซที่เรียบง่ายสำหรับการรับโค้ดเร่งรัดของ CPU เพื่อรันบนคอร์ทั้งหมด (node.js และเว็บ)

ตัวอย่างจะเป็น

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 มากเกินไปสำหรับสิ่งเดียวกันหรือค้นหาแฮ็กบางอย่าง

คุณลักษณะหนึ่งที่นำมาใช้ใน HTML5 คือWeb Workersซึ่ง JavaScript ทำงานอยู่เบื้องหลังโดยไม่ขึ้นกับสคริปต์อื่น ๆ โดยไม่ส่งผลกระทบต่อประสิทธิภาพของหน้า

ได้รับการสนับสนุนในเกือบทุกเบราว์เซอร์:

Chrome - 4.0+

IE - 10.0+

Mozilla - 3.5+

Safari - 4.0+

Opera - 11.5+

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.