JavaScript เป็นที่รู้จักกันว่าเป็นเธรดเดี่ยวในการใช้งานเบราว์เซอร์สมัยใหม่ทั้งหมด แต่มีการระบุไว้ในมาตรฐานใด ๆ หรือเป็นเพียงประเพณี? ปลอดภัยไหมที่จะถือว่า JavaScript เป็นเธรดเดียว?
JavaScript เป็นที่รู้จักกันว่าเป็นเธรดเดี่ยวในการใช้งานเบราว์เซอร์สมัยใหม่ทั้งหมด แต่มีการระบุไว้ในมาตรฐานใด ๆ หรือเป็นเพียงประเพณี? ปลอดภัยไหมที่จะถือว่า JavaScript เป็นเธรดเดียว?
คำตอบ:
นั่นเป็นคำถามที่ดี ฉันชอบที่จะพูดว่า“ ใช่” ฉันทำไม่ได้
โดยปกติแล้ว JavaScript จะมีการเรียกใช้งานสคริปต์ของเธรด (*) เพียงครั้งเดียวดังนั้นเมื่อป้อนสคริปต์แบบอินไลน์ผู้ฟังเหตุการณ์หรือการหมดเวลาของคุณคุณจะยังคงอยู่ในการควบคุมอย่างสมบูรณ์จนกว่าคุณจะกลับมาจากจุดสิ้นสุดของบล็อกหรือฟังก์ชัน
(*: เพิกเฉยต่อคำถามว่าเบราว์เซอร์ใช้งานเอ็นจิน JS ของตนโดยใช้หนึ่ง OS-thread หรือไม่หรือ WebWorkers มีการ จำกัด เธรดการดำเนินการที่ จำกัด อื่น ๆ )
อย่างไรก็ตามในความเป็นจริงมันไม่เป็นความจริงในทางที่น่ารังเกียจ
กรณีที่พบบ่อยที่สุดคือเหตุการณ์ทันที เบราว์เซอร์จะเริ่มต้นสิ่งเหล่านี้ทันทีเมื่อโค้ดของคุณทำสิ่งที่ทำให้เกิด:
var l= document.getElementById('log');
var i= document.getElementById('inp');
i.onblur= function() {
l.value+= 'blur\n';
};
setTimeout(function() {
l.value+= 'log in\n';
l.focus();
l.value+= 'log out\n';
}, 100);
i.focus();
<textarea id="log" rows="20" cols="40"></textarea>
<input id="inp">
ให้ผลลัพธ์log in, blur, log outกับทุกคนยกเว้น IE เหตุการณ์เหล่านี้ไม่เพียงเกิดขึ้นเพราะคุณโทรติดต่อfocus()โดยตรงเหตุการณ์เหล่านี้อาจเกิดขึ้นเพราะคุณโทรalert()หรือเปิดหน้าต่างป๊อปอัปหรือสิ่งอื่นใดที่ขยับโฟกัส
นอกจากนี้ยังสามารถส่งผลให้เกิดเหตุการณ์อื่น ๆ ตัวอย่างเช่นเพิ่มi.onchangeผู้ฟังและพิมพ์บางสิ่งบางอย่างในอินพุตก่อนที่การfocus()โทรจะแยกออกจากกันและลำดับของการบันทึกคือlog in, change, blur, log outยกเว้นใน Opera ที่เป็นlog in, blur, log out, changeและ IE อยู่ที่log in, change, log out, blurใด
ในทำนองเดียวกันการเรียกclick()องค์ประกอบที่ให้มันเรียกonclickตัวจัดการทันทีในเบราว์เซอร์ทั้งหมด (อย่างน้อยก็สอดคล้องกัน!)
(ฉันใช้on...คุณสมบัติตัวจัดการเหตุการณ์โดยตรงที่นี่ แต่สิ่งเดียวกันเกิดขึ้นกับaddEventListenerและattachEvent)
นอกจากนี้ยังมีสถานการณ์อีกหลายเหตุการณ์ที่เหตุการณ์สามารถเกิดขึ้นได้ในขณะที่โค้ดของคุณถูกเกลียวแม้ว่าคุณจะไม่ได้ทำอะไรเลยเพื่อกระตุ้นมัน ตัวอย่าง:
var l= document.getElementById('log');
document.getElementById('act').onclick= function() {
l.value+= 'alert in\n';
alert('alert!');
l.value+= 'alert out\n';
};
window.onresize= function() {
l.value+= 'resize\n';
};
<textarea id="log" rows="20" cols="40"></textarea>
<button id="act">alert</button>
กดalertและคุณจะได้รับกล่องโต้ตอบเป็นกิริยาช่วย ไม่มีสคริปต์ทำงานอีกต่อไปจนกว่าคุณจะยกเลิกบทสนทนานั้นใช่ไหม Nope ปรับขนาดหน้าต่างหลักและคุณจะได้รับalert in, resize, alert outใน textarea
คุณอาจคิดว่ามันเป็นไปไม่ได้ที่จะปรับขนาดหน้าต่างในขณะที่กล่องโต้ตอบโมดอลติดอยู่ แต่ไม่ใช่: ใน Linux คุณสามารถปรับขนาดหน้าต่างได้มากเท่าที่คุณต้องการ บน Windows มันไม่ง่ายนัก แต่คุณสามารถทำได้โดยเปลี่ยนความละเอียดหน้าจอจากใหญ่ขึ้นเป็นเล็กลงที่หน้าต่างไม่พอดีทำให้ปรับขนาดได้
คุณอาจคิดว่ามันเป็นเพียงresize(และอาจจะมีอีกสองสามอย่างscroll) ที่สามารถเริ่มทำงานเมื่อผู้ใช้ไม่มีการโต้ตอบกับเบราว์เซอร์เนื่องจากสคริปต์เป็นเธรด และสำหรับหน้าต่างเดียวคุณอาจพูดถูก แต่ทุกอย่างก็จะเพิ่มขึ้นทันทีที่คุณทำการเขียนสคริปต์ข้ามหน้าต่าง สำหรับเบราว์เซอร์อื่น ๆ นอกเหนือจาก Safari ซึ่งบล็อกหน้าต่าง / แท็บ / เฟรมทั้งหมดเมื่อใดก็ตามที่ไม่ว่างคุณสามารถโต้ตอบกับเอกสารจากรหัสของเอกสารอื่นโดยใช้เธรดการดำเนินการแยกต่างหากและทำให้ตัวจัดการเหตุการณ์ที่เกี่ยวข้อง ไฟ.
สถานที่ที่สามารถสร้างเหตุการณ์ที่คุณสามารถสร้างได้ในขณะที่สคริปต์ยังคงอยู่ในเธรด:
เมื่อป๊อปอัปกิริยา ( alert, confirm, prompt) จะเปิดในเบราว์เซอร์ทั้งหมด แต่โอเปร่า;
ระหว่างshowModalDialogเบราว์เซอร์ที่รองรับ
กล่องโต้ตอบ“ สคริปต์ในหน้านี้อาจไม่ว่าง ... ” แม้ว่าคุณจะเลือกที่จะให้สคริปต์ทำงานต่อไปได้อนุญาตให้เหตุการณ์เช่นปรับขนาดและเบลอไล่และจัดการแม้ในขณะที่สคริปต์อยู่ตรงกลาง ไม่ว่างในวงยกเว้นใน Opera
เมื่อสักครู่ที่ผ่านมาสำหรับฉันใน IE ด้วยปลั๊กอิน Sun Java การเรียกใช้วิธีการใด ๆ บนแอปเพล็ตอาจทำให้เหตุการณ์เริ่มทำงานและสคริปต์จะถูกป้อนอีกครั้ง นี่เป็นข้อผิดพลาดตามกำหนดเวลาเสมอและเป็นไปได้ที่ Sun จะแก้ไขมันตั้งแต่ (ฉันหวังอย่างนั้น)
อาจจะมากกว่า ไม่นานมานี้ที่ฉันทำการทดสอบและเบราว์เซอร์มีความซับซ้อนเพิ่มขึ้น
โดยสรุปแล้ว JavaScript จะปรากฏต่อผู้ใช้ส่วนใหญ่โดยส่วนใหญ่แล้วจะมีการดำเนินการเธรดเดี่ยวที่เข้มงวดซึ่งควบคุมโดยเหตุการณ์ ในความเป็นจริงมันไม่มีสิ่งนั้น ไม่ชัดเจนว่านี่เป็นเพียงข้อผิดพลาดและการออกแบบโดยเจตนาเท่าใด แต่ถ้าคุณกำลังเขียนแอพพลิเคชั่นที่ซับซ้อนโดยเฉพาะอย่างยิ่ง cross-window / frame-scripting script มีโอกาสที่มันจะกัดคุณ - และเป็นระยะ ๆ วิธีที่ยากต่อการแก้ไขข้อบกพร่อง
หากสิ่งที่เลวร้ายที่สุดมาถึงจุดที่เลวร้ายที่สุดคุณสามารถแก้ปัญหาการเกิดพร้อมกันได้โดยการตอบกลับเหตุการณ์โดยอ้อมทั้งหมด เมื่อมีเหตุการณ์เข้ามาให้วางลงในคิวและจัดการกับคิวตามลำดับในภายหลังในsetIntervalฟังก์ชั่น หากคุณกำลังเขียนเฟรมเวิร์กที่คุณตั้งใจจะใช้โดยแอพพลิเคชั่นที่ซับซ้อนการทำเช่นนี้อาจเป็นเรื่องดี postMessageหวังว่าจะบรรเทาความเจ็บปวดจากการเขียนสคริปต์ข้ามเอกสารในอนาคต
blur หลังจากรหัสของคุณคืนการควบคุมไปยังเบราว์เซอร์
ฉันจะบอกว่าใช่ - เพราะจาวาสคริปต์จาวาสคริปต์ที่มีอยู่ (อย่างน้อยก็ไม่สำคัญ) ที่มีอยู่ทั้งหมดจะแตกถ้าเครื่องมือจาวาสคริปต์ของเบราว์เซอร์นั้นทำงานแบบอะซิงโครนัส
เพิ่มไปที่ข้อเท็จจริงที่ว่าHTML5 ระบุ Web Workers (API มาตรฐานที่ชัดเจนสำหรับโค้ดจาวาสคริปต์แบบมัลติเธรด) ที่แนะนำมัลติเธรดลงใน Javascript พื้นฐานจะไม่มีประโยชน์ส่วนใหญ่
( หมายเหตุถึงผู้แสดงความคิดเห็นอื่น ๆ :ถึงแม้ว่าsetTimeout/setIntervalเหตุการณ์ HTTP onload ขอ (XHR) และกิจกรรม UI (คลิก, โฟกัส, ฯลฯ ) ให้การแสดงผลที่หยาบคายของมัลติเธรด - พวกเขายังคงดำเนินการทั้งหมดในไทม์ไลน์เดียว - หนึ่งที่ เวลา - ดังนั้นแม้ว่าเราจะไม่ทราบคำสั่งดำเนินการของพวกเขาล่วงหน้าไม่จำเป็นต้องกังวลเกี่ยวกับสภาพภายนอกที่เปลี่ยนแปลงในระหว่างการดำเนินการของตัวจัดการเหตุการณ์ฟังก์ชั่นที่กำหนดเวลาหรือโทรกลับ XHR)
ใช่แม้ว่าคุณยังสามารถประสบปัญหาของการเขียนโปรแกรมพร้อมกัน (ส่วนใหญ่เป็นเงื่อนไขการแข่งขัน) เมื่อใช้ API แบบอะซิงโครนัสใด ๆ เช่น setInterval และการเรียกกลับ xmlhttp
ใช่แม้ว่า Internet Explorer 9 จะรวบรวม Javascript ของคุณในเธรดแยกต่างหากเพื่อเตรียมการดำเนินการกับเธรดหลัก สิ่งนี้ไม่ได้เปลี่ยนแปลงอะไรสำหรับคุณในฐานะโปรแกรมเมอร์
ฉันจะบอกว่าสเปคไม่ได้ป้องกันใครบางคนจากการสร้างเครื่องมือที่ใช้งานจาวาสคริปต์ในหลายหัวข้อที่ต้องใช้รหัสในการทำข้อมูลให้ตรงกันสำหรับการเข้าถึงสถานะวัตถุที่ใช้ร่วมกัน
ฉันคิดว่ากระบวนทัศน์ที่ไม่มีการบล็อกเธรดเดี่ยวมาจากความต้องการใช้งานจาวาสคริปต์ในเบราว์เซอร์ที่ UI ไม่ควรบล็อก
Nodejsได้ปฏิบัติตามแนวทางของเบราว์เซอร์แล้ว
แรดเครื่องยนต์ แต่สนับสนุนการทำงาน js รหัสในหัวข้อที่แตกต่างกัน การประหารชีวิตไม่สามารถแบ่งปันบริบท แต่พวกเขาสามารถแบ่งปันขอบเขต สำหรับกรณีเฉพาะนี้เอกสารระบุสถานะ:
... "แรดรับประกันว่าการเข้าถึงคุณสมบัติของวัตถุ JavaScript นั้นเป็นอะตอมมิกข้ามเธรด แต่ไม่รับประกันอะไรอีกต่อไปสำหรับสคริปต์ที่ดำเนินการในขอบเขตเดียวกันในเวลาเดียวกันหากสคริปต์สองตัวใช้ขอบเขตเดียวกันพร้อมกันสคริปต์จะ รับผิดชอบการประสานงานการเข้าถึงตัวแปรที่แชร์ "
จากการอ่านเอกสารของแรดฉันสรุปได้ว่าบางคนสามารถเขียน API ของ javascript ที่วางไข่เธรด javascript ใหม่ แต่ api นั้นเป็นแรดเฉพาะ (เช่นโหนดสามารถวางไข่กระบวนการใหม่)
ฉันคิดว่าถึงแม้จะเป็นเอ็นจิ้นที่รองรับหลายเธรดในจาวาสคริปต์ก็ควรมีความเข้ากันได้กับสคริปต์ที่ไม่ได้พิจารณาว่ามีหลายเธรดหรือบล็อก
การสร้างเบราว์เซอร์และโหนดให้เป็นแบบที่ฉันเห็นคือ:
ดังนั้นในกรณีของเบราว์เซอร์และ nodejs (และอาจจะมากของเครื่องมืออื่น ๆ ) จาวาสคริปต์ไม่ได้ multithreaded แต่เครื่องยนต์ตัวเองเป็น
การปรากฏตัวของคนทำงานเว็บนั้นพิสูจน์ได้ว่าจาวาสคริปต์สามารถเป็นแบบมัลติเธรดในแง่ที่ว่าบางคนสามารถสร้างรหัสในจาวาสคริปต์ที่จะทำงานบนเธรดแยกต่างหาก
อย่างไรก็ตาม: ผู้ทำงานกับเว็บจะไม่แก้ปัญหาของเธรดดั้งเดิมที่สามารถแบ่งปันบริบทการดำเนินการได้ กฎที่ 2 และ 3 ข้างต้นยังคงมีผลอยู่แต่คราวนี้โค้ดเธรดถูกสร้างโดยผู้ใช้ (ผู้เขียนโค้ด js) ในจาวาสคริปต์
สิ่งเดียวที่ต้องพิจารณาคือจำนวนเธรดที่สร้างใหม่จากมุมมองประสิทธิภาพ (และไม่ใช่การทำงานพร้อมกัน ) ดูด้านล่าง:
อินเทอร์เฟซผู้ทำงานจะสร้างเธรดระดับ OS จริงและโปรแกรมเมอร์ที่ใส่ใจอาจกังวลว่าการเกิดพร้อมกันอาจทำให้เอฟเฟกต์ "น่าสนใจ" ในรหัสของคุณหากคุณไม่ระวัง
แต่เนื่องจากคนงานเว็บได้อย่างรอบคอบควบคุมจุดสื่อสารกับหัวข้ออื่น ๆ ก็จริงมากยากที่จะทำให้เกิดปัญหาการทำงานพร้อมกัน ไม่มีการเข้าถึงคอมโพเนนต์ที่ไม่ใช่ threadsafe หรือ DOM และคุณต้องส่งผ่านข้อมูลเฉพาะเข้าและออกจากเธรดผ่านวัตถุที่ต่อเนื่องกัน ดังนั้นคุณต้องทำงานอย่างหนักเพื่อทำให้เกิดปัญหาในรหัสของคุณ
นอกจากทฤษฎีแล้วให้เตรียมพร้อมเกี่ยวกับมุมและข้อผิดพลาดที่อธิบายไว้ในคำตอบที่ยอมรับได้เสมอ
JavaScript / ECMAScript ถูกออกแบบมาให้ใช้งานได้จริงภายในสภาพแวดล้อมโฮสต์ นั่นคือ JavaScript ไม่ได้ทำอะไรเลยเว้นแต่ว่าสภาพแวดล้อมของโฮสต์ตัดสินใจที่จะแยกวิเคราะห์และดำเนินการสคริปต์ที่กำหนดและให้วัตถุสภาพแวดล้อมที่ให้ JavaScript มีประโยชน์จริง ๆ (เช่น DOM ในเบราว์เซอร์)
ฉันคิดว่าฟังก์ชั่นหรือสคริปต์ที่กำหนดจะดำเนินการแบบบรรทัดต่อบรรทัดและรับประกันสำหรับ JavaScript อย่างไรก็ตามอาจจะมีสภาพแวดล้อมโฮสต์ที่สามารถเรียกใช้งานหลายสคริปต์ได้ในเวลาเดียวกัน หรือสภาพแวดล้อมโฮสต์สามารถให้วัตถุที่มีหลายเธรด setTimeoutและsetIntervalเป็นตัวอย่างหรืออย่างน้อยหลอกตัวอย่างของสภาพแวดล้อมโฮสต์ให้วิธีการทำพร้อมกันบางอย่าง (แม้ว่าจะไม่เห็นพ้องด้วยกัน)
ที่จริงแล้วหน้าต่างหลักสามารถสื่อสารกับหน้าต่างลูกหรือพี่น้องหรือเฟรมที่มีเธรดการดำเนินการของตัวเองทำงาน
@ Bobince ได้ให้คำตอบที่ทึบแสงจริงๆ
Riffing จากคำตอบของMárÖrlygsson, Javascript นั้นเป็นแบบเธรดเดียวเสมอเนื่องจากข้อเท็จจริงง่าย ๆ นี้: ทุกอย่างใน Javascript นั้นถูกเรียกใช้งานตามไทม์ไลน์เดียว
นั่นคือคำจำกัดความที่เข้มงวดของภาษาการเขียนโปรแกรมแบบเธรดเดียว
เลขที่
ฉันจะต่อต้านฝูงชนที่นี่ แต่ทนกับฉัน สคริปต์ JS เดียวมีวัตถุประสงค์เพื่อให้มีเธรดเดี่ยวที่มีประสิทธิภาพแต่นี่ไม่ได้หมายความว่าสคริปต์นั้นไม่สามารถตีความได้แตกต่างกัน
สมมติว่าคุณมีรหัสต่อไปนี้ ...
var list = [];
for (var i = 0; i < 10000; i++) {
list[i] = i * i;
}
สิ่งนี้ถูกเขียนด้วยความคาดหวังว่าในตอนท้ายของลูปรายการจะต้องมี 10,000 รายการซึ่งเป็นดัชนีกำลังสอง แต่ VM อาจสังเกตเห็นว่าการวนซ้ำของลูปแต่ละครั้งไม่ส่งผลกระทบต่ออีกและตีความอีกครั้งโดยใช้สองเธรด
หัวข้อแรก
for (var i = 0; i < 5000; i++) {
list[i] = i * i;
}
กระทู้ที่สอง
for (var i = 5000; i < 10000; i++) {
list[i] = i * i;
}
ฉันทำให้มันง่ายขึ้นที่นี่เพราะ JS arrays นั้นซับซ้อนกว่าและเป็นหน่วยความจำที่โง่เง่า แต่ถ้าสคริปต์ทั้งสองนี้สามารถเพิ่มรายการในอาเรย์ด้วยวิธีที่ปลอดภัยต่อเธรดดังนั้นเมื่อถึงเวลาที่ทั้งสองดำเนินการเสร็จแล้ว ผลลัพธ์เดียวกันกับเวอร์ชันที่มีเธรดเดียว
ในขณะที่ฉันไม่ทราบว่า VM ตรวจจับโค้ดแบบขนานได้เช่นนี้ดูเหมือนว่าอาจมีอยู่ในอนาคตสำหรับ JIT VM เนื่องจากอาจให้ความเร็วที่มากกว่าในบางสถานการณ์
การนำแนวคิดนี้ไปใช้เพิ่มเติมอาจเป็นไปได้ว่าโค้ดอาจมีคำอธิบายประกอบเพื่อให้ VM ทราบว่าควรแปลงอะไรเป็นโค้ดแบบมัลติเธรด
// like "use strict" this enables certain features on compatible VMs.
"use parallel";
var list = [];
// This string, which has no effect on incompatible VMs, enables threading on
// this loop.
"parallel for";
for (var i = 0; i < 10000; i++) {
list[i] = i * i;
}
เนื่องจาก Web Workers กำลังมาถึง Javascript มันไม่น่าเป็นไปได้ที่ระบบ uglier นี้จะมีอยู่ แต่ฉันคิดว่ามันปลอดภัยที่จะพูดว่า Javascript นั้นเป็นเธรดเดี่ยวตามประเพณี
Chrome เป็นมัลติโปรเซสเซอร์และฉันคิดว่าทุกกระบวนการเกี่ยวข้องกับรหัส Javascript ของตัวเอง แต่เท่าที่โค้ดนั้นรู้ก็คือ
ไม่มีการสนับสนุนใด ๆ ใน Javascript สำหรับมัลติเธรดอย่างน้อยไม่ชัดเจนดังนั้นจึงไม่ได้สร้างความแตกต่าง
ฉันได้ลองตัวอย่าง @ Bobince ด้วยการแก้ไขเล็กน้อย:
<html>
<head>
<title>Test</title>
</head>
<body>
<textarea id="log" rows="20" cols="40"></textarea>
<br />
<button id="act">Run</button>
<script type="text/javascript">
let l= document.getElementById('log');
let b = document.getElementById('act');
let s = 0;
b.addEventListener('click', function() {
l.value += 'click begin\n';
s = 10;
let s2 = s;
alert('alert!');
s = s + s2;
l.value += 'click end\n';
l.value += `result = ${s}, should be ${s2 + s2}\n`;
l.value += '----------\n';
});
window.addEventListener('resize', function() {
if (s === 10) {
s = 5;
}
l.value+= 'resize\n';
});
</script>
</body>
</html>
ดังนั้นเมื่อคุณกดเรียกใช้ปิดป๊อปอัปการแจ้งเตือนและทำ "เธรดเดียว" คุณควรเห็นบางสิ่งเช่นนี้:
click begin
click end
result = 20, should be 20
แต่ถ้าคุณพยายามเรียกใช้งานใน Opera หรือ Firefox บน Windows และย่อ / ขยายหน้าต่างด้วยป๊อปอัพแจ้งเตือนบนหน้าจอก็จะมีดังนี้:
click begin
resize
click end
result = 15, should be 20
ฉันไม่ต้องการพูดว่านี่คือ "multithreading" แต่บางส่วนของรหัสได้ดำเนินการในเวลาที่ผิดกับฉันไม่ได้คาดหวังสิ่งนี้และตอนนี้ฉันมีสถานะที่เสียหาย และดีกว่าที่จะรู้เกี่ยวกับพฤติกรรมนี้
พยายามซ้อนฟังก์ชั่น setTimeout สองตัวในฟังก์ชันซึ่งจะทำงานแบบมัลติเธรด (เช่นตัวจับเวลาภายนอกจะไม่รอให้ตัวประมวลผลภายในเสร็จสิ้นก่อนที่จะเรียกใช้ฟังก์ชัน)
setTimeout(function(){setTimeout(function(){console.log('i herd you liek async')}, 0); alert('yo dawg!')}, 0)(สำหรับการบันทึก yo dawg ควรจะมาก่อนเสมอจากนั้นคอนโซลเอาท์พุทจะบันทึก)