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 ควรจะมาก่อนเสมอจากนั้นคอนโซลเอาท์พุทจะบันทึก)