ทำความเข้าใจกับ Event Loop


135

ฉันกำลังคิดถึงมันและนี่คือสิ่งที่ฉันคิดขึ้นมา:

ลองดูโค้ดด้านล่างนี้:

console.clear();
console.log("a");
setTimeout(function(){console.log("b");},1000);
console.log("c");
setTimeout(function(){console.log("d");},0);

มีคำขอเข้ามาและเอ็นจิน JS เริ่มดำเนินการโค้ดด้านบนทีละขั้นตอน สองสายแรกคือสายซิงค์ แต่เมื่อพูดถึงsetTimeoutวิธีการมันจะกลายเป็นการดำเนินการแบบ async แต่ JS กลับมาจากมันทันทีและดำเนินการต่อซึ่งเรียกว่าNon-BlockingหรือAsync. และมันยังคงทำงานอื่น ๆ

ผลลัพธ์ของการดำเนินการนี้มีดังต่อไปนี้:

acdb

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

เรากำลังพูดถึงแอปพลิเคชันเธรดเดียวที่นี่ JS Engine ยังคงดำเนินการสิ่งนี้ต่อไปและเว้นแต่ว่าจะเสร็จสิ้นคำขอแรกจะไม่ไปที่คำขอที่สอง แต่สิ่งที่ดีคือมันจะไม่รอให้การดำเนินการบล็อกเหมือนsetTimeoutจะแก้ไขดังนั้นมันจะเร็วกว่าเพราะยอมรับคำขอที่เข้ามาใหม่

แต่คำถามของฉันเกิดขึ้นจากรายการต่อไปนี้:

# 1:หากเรากำลังพูดถึงแอปพลิเคชันแบบเธรดเดียวกลไกใดที่ประมวลผลsetTimeoutsในขณะที่เอ็นจิ้น JS ยอมรับคำขอเพิ่มเติมและดำเนินการ เธรดเดี่ยวทำงานต่อไปยังคำขออื่น ๆ อย่างไร สิ่งที่ใช้ได้ผลในsetTimeoutขณะที่คำขออื่นเข้ามาและดำเนินการ

# 2:หากsetTimeoutฟังก์ชันเหล่านี้ถูกเรียกใช้งานเบื้องหลังในขณะที่มีคำขอเข้ามาและกำลังดำเนินการมากขึ้นสิ่งที่ดำเนินการประมวลผลแบบ async อยู่เบื้องหลัง? สิ่งที่เราพูดถึงนี้เรียกว่าEventLoopอะไร?

# 3:แต่ไม่ควรใส่วิธีการทั้งหมดEventLoopเพื่อให้สิ่งทั้งหมดถูกดำเนินการและเรียกวิธีการโทรกลับ? นี่คือสิ่งที่ฉันเข้าใจเมื่อพูดถึงฟังก์ชันการโทรกลับ:

function downloadFile(filePath, callback)
{
   blah.downloadFile(filePath);
   callback();
}

แต่ในกรณีนี้ JS Engine จะรู้ได้อย่างไรว่าเป็นฟังก์ชัน async เพื่อให้สามารถเรียกกลับได้EventLoopหรือไม่? บางทีบางอย่างเช่นasyncคำสำคัญใน C # หรือแอตทริบิวต์บางประเภทที่ระบุว่าวิธีการที่ JS Engine จะใช้นั้นเป็นวิธีการแบบ async และควรได้รับการปฏิบัติตามนั้น

# 4:แต่บทความพูดค่อนข้างตรงกันข้ามกับสิ่งที่ฉันคาดเดาว่าสิ่งต่างๆจะทำงานอย่างไร:

Event Loop คือคิวของฟังก์ชันเรียกกลับ เมื่อฟังก์ชัน async ทำงานฟังก์ชันเรียกกลับจะถูกผลักเข้าไปในคิว เอ็นจิ้น JavaScript ไม่เริ่มประมวลผลการวนรอบเหตุการณ์จนกว่าโค้ดหลังจากที่ฟังก์ชัน async ทำงานแล้ว

# 5:และมีภาพนี้อยู่ที่นี่ซึ่งอาจเป็นประโยชน์ แต่คำอธิบายแรกในภาพบอกว่าสิ่งเดียวกับที่กล่าวถึงในคำถามข้อ 4:

ป้อนคำอธิบายภาพที่นี่

ดังนั้นคำถามของฉันที่นี่คือการได้รับคำชี้แจงเกี่ยวกับรายการข้างต้น?


1
เธรดไม่ใช่คำเปรียบเทียบที่เหมาะสมในการจัดการปัญหาเหล่านั้น คิดถึงเหตุการณ์
Denys Séguret

1
@dystroy: ตัวอย่างโค้ดเพื่อแสดงการเปรียบเทียบเหตุการณ์ใน JS น่าดู
Tarik

ฉันไม่เห็นคำถามของคุณตรงนี้
Denys Séguret

1
@dystroy: คำถามของฉันที่นี่คือการได้รับคำชี้แจงเกี่ยวกับรายการข้างต้นหรือไม่?
Tarik

2
โหนดไม่ได้มีเธรดเดียว แต่ไม่สำคัญสำหรับคุณ (นอกเหนือจากข้อเท็จจริงที่ว่ามันสามารถจัดการสิ่งอื่น ๆ ให้เสร็จได้ในขณะที่รหัสผู้ใช้ของคุณดำเนินการ) การเรียกกลับมากที่สุดเพียงครั้งเดียวในรหัสผู้ใช้ของคุณจะถูกเรียกใช้ในครั้งเดียว
Denys Séguret

คำตอบ:


85

1: หากเรากำลังพูดถึงแอปพลิเคชันแบบเธรดเดียวกระบวนการ setTimeouts ในขณะที่เอ็นจิ้น JS ยอมรับคำขอเพิ่มเติมและดำเนินการอย่างไร เธรดเดี่ยวนั้นจะทำงานต่อไปตามคำขออื่น ๆ ไม่ใช่หรือ จากนั้นใครจะทำงานต่อใน setTimeout ในขณะที่คำขออื่น ๆ ยังคงมาและดำเนินการ

มีเธรดเพียง 1 เธรดในกระบวนการโหนดที่จะรัน JavaScript ของโปรแกรมของคุณ อย่างไรก็ตามภายในโหนดเองมีหลายเธรดที่จัดการการดำเนินการของกลไกลูปเหตุการณ์ซึ่งรวมถึงกลุ่มของเธรด IO และอีกจำนวนหนึ่ง คีย์คือจำนวนเธรดเหล่านี้ไม่ตรงกับจำนวนของการเชื่อมต่อพร้อมกันที่ถูกจัดการเหมือนกับที่ทำในโมเดลการทำงานพร้อมกันของเธรดต่อการเชื่อมต่อ

ตอนนี้เกี่ยวกับ "การเรียกใช้ setTimeouts" เมื่อคุณเรียกใช้ setTimeoutโหนดทั้งหมดจะอัปเดตโครงสร้างข้อมูลของฟังก์ชันที่จะดำเนินการพร้อมกันในอนาคต โดยพื้นฐานแล้วจะมีคิวของสิ่งที่ต้องทำและทุกๆ "ติ๊ก" ของเหตุการณ์ที่วนซ้ำจะเลือกหนึ่งรายการลบออกจากคิวและเรียกใช้

สิ่งสำคัญที่ต้องเข้าใจคือโหนดต้องอาศัยระบบปฏิบัติการสำหรับการยกของหนักส่วนใหญ่ ดังนั้นคำขอเครือข่ายที่เข้ามาจึงถูกติดตามโดยระบบปฏิบัติการเองและเมื่อโหนดพร้อมที่จะจัดการระบบก็เพียงแค่ใช้การเรียกระบบเพื่อขอระบบปฏิบัติการสำหรับคำขอเครือข่ายที่มีข้อมูลพร้อมที่จะประมวลผล โหนด "ที่ทำงาน" ของ IO จำนวนมากมีเพียง "เฮ้ OS มีการเชื่อมต่อเครือข่ายพร้อมข้อมูลพร้อมที่จะอ่านหรือไม่" หรือ "เฮ้ OS การเรียกระบบไฟล์ที่ค้างอยู่ของฉันมีข้อมูลพร้อมหรือยัง" ขึ้นอยู่กับอัลกอริทึมภายในและการออกแบบกลไกการวนซ้ำเหตุการณ์โหนดจะเลือก "ติ๊ก" ของ JavaScript เพื่อดำเนินการเรียกใช้จากนั้นทำซ้ำขั้นตอนทั้งหมดอีกครั้ง นั่นคือความหมายของลูปเหตุการณ์ โดยพื้นฐานแล้ว Node จะเป็นตัวกำหนด "JavaScript อีกเล็กน้อยที่ฉันควรเรียกใช้" จากนั้นเรียกใช้setTimeoutหรือprocess.nextTick.

2: หาก setTimeout เหล่านี้จะถูกดำเนินการเบื้องหลังในขณะที่มีคำขอเข้ามาและถูกดำเนินการมากขึ้นสิ่งที่ดำเนินการประมวลผลแบบ async เบื้องหลังคือสิ่งที่เรากำลังพูดถึง EventLoop?

ไม่มีการเรียกใช้ JavaScript เบื้องหลัง JavaScript ทั้งหมดในโปรแกรมของคุณจะทำงานด้านหน้าและตรงกลางทีละรายการ สิ่งที่เกิดขึ้นเบื้องหลังคือ OS จัดการ IO และโหนดรอให้พร้อมและโหนดจัดการคิวของจาวาสคริปต์ที่รอดำเนินการ

3: JS Engine รู้ได้อย่างไรว่าเป็นฟังก์ชัน async เพื่อให้สามารถใส่ใน EventLoop ได้หรือไม่

มีชุดฟังก์ชันคงที่ในโหนดคอร์ที่เป็น async เนื่องจากทำการเรียกระบบและโหนดรู้ว่าสิ่งเหล่านี้เป็นเพราะต้องเรียกใช้ OS หรือ C ++ โดยทั่วไปเครือข่ายและระบบไฟล์ทั้งหมด IO ตลอดจนการโต้ตอบของกระบวนการย่อยจะเป็นแบบอะซิงโครนัสและวิธีเดียวที่ JavaScript สามารถรับโหนดเพื่อเรียกใช้บางสิ่งแบบอะซิงโครนัสได้คือการเรียกใช้ฟังก์ชัน async ที่จัดเตรียมโดยไลบรารีหลักของโหนด แม้ว่าคุณจะใช้แพ็กเกจ npm ที่กำหนด API ของตัวเองเพื่อให้เกิดเหตุการณ์ลูปในที่สุดโค้ดของแพ็กเกจ npm จะเรียกหนึ่งในฟังก์ชัน async ของโหนดคอร์และนั่นคือเมื่อโหนดทราบว่าติ๊กเสร็จสมบูรณ์และสามารถเริ่มเหตุการณ์ได้ อัลกอริทึมวนซ้ำอีกครั้ง

4 The Event Loop คือคิวของฟังก์ชันเรียกกลับ เมื่อฟังก์ชัน async ทำงานฟังก์ชันเรียกกลับจะถูกผลักเข้าไปในคิว เอ็นจิ้น JavaScript ไม่เริ่มประมวลผลการวนรอบเหตุการณ์จนกว่าโค้ดหลังจากที่ฟังก์ชัน async ทำงานแล้ว

ใช่นี่เป็นเรื่องจริง แต่ทำให้เข้าใจผิด สิ่งสำคัญคือรูปแบบปกติคือ:

//Let's say this code is running in tick 1
fs.readFile("/home/barney/colors.txt", function (error, data) {
  //The code inside this callback function will absolutely NOT run in tick 1
  //It will run in some tick >= 2
});
//This code will absolutely also run in tick 1
//HOWEVER, typically there's not much else to do here,
//so at some point soon after queueing up some async IO, this tick
//will have nothing useful to do so it will just end because the IO result
//is necessary before anything useful can be done

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


1
สมมติว่าฉันมีคิวที่จะใช้เวลา 1 นาทีในการดำเนินการเซิร์ฟเวอร์และสิ่งแรกคือฟังก์ชัน async ที่ทำงานเสร็จหลังจาก 10 วินาที มันจะไปถึงจุดสิ้นสุดของคิวหรือจะผลักดันตัวเองเข้าไปในบรรทัดทันทีที่พร้อม?
ilyo

4
โดยทั่วไปมันจะไปที่ส่วนท้ายของคิว แต่ความหมายของprocess.nextTickvs setTimeoutvs setImmediateนั้นแตกต่างกันอย่างละเอียดแม้ว่าคุณจะไม่ต้องสนใจก็ตาม ฉันมีบล็อกโพสต์ชื่อ setTimeout และเพื่อน ๆที่ลงรายละเอียดเพิ่มเติม
Peter Lyons

ช่วยอธิบายให้ละเอียดหน่อยได้ไหม? สมมติว่าฉันมีการโทรกลับสองครั้งและอันแรกมีเมธอด changeColor ที่มีเวลาดำเนินการ 10mS และ setTimeout 1 นาทีและครั้งที่สองมีเมธอด changeBackground ที่มีเวลาดำเนินการ 50ms พร้อม setTimeout 10 วินาที ฉันรู้สึกถึงการเปลี่ยนแปลงพื้นหลังโดยอยู่ในคิวก่อนและ changeColor จะเป็นถัดไป หลังจากนั้นลูปเหตุการณ์จะเลือกวิธีการพร้อมกัน ฉันถูกไหม?
SheshPai

1
@SheshPai มันสับสนเกินไปสำหรับทุกคนที่จะพูดคุยเกี่ยวกับรหัสเมื่อเขียนเป็นย่อหน้าของภาษาอังกฤษ เพียงโพสต์คำถามใหม่พร้อมข้อมูลโค้ดเพื่อให้ผู้ใช้สามารถตอบโดยอิงตามโค้ดแทนที่จะเป็นคำอธิบายโค้ดซึ่งทำให้เกิดความคลุมเครือมากมาย
Peter Lyons

youtube.com/watch?v=QyUFheng6J0&spfreload=5นี่เป็นอีกหนึ่งคำอธิบายที่ดีของ JavaScript Engine
Mukesh Kumar

65

มีวิดีโอสอนการใช้งานที่ยอดเยี่ยมโดย Philip Roberts ซึ่งอธิบายการวนซ้ำเหตุการณ์ของจาวาสคริปต์ด้วยวิธีที่ง่ายและเป็นแนวคิดที่สุด นักพัฒนาจาวาสคริปต์ทุกคนควรมีลักษณะ

นี่คือลิงค์วิดีโอบน Youtube


16
ฉันดูมันและมันเป็นคำอธิบายที่ดีที่สุดเท่าที่เคยมีมา
Tarik

1
ต้องดูวิดีโอสำหรับแฟน JavaScript และผู้ที่ชื่นชอบ
Nirus

1
วิดีโอนี้เปลี่ยนการแสดงสดของฉัน ^^
HuyTran

1
มาหลงที่นี่ .. และนี่คือหนึ่งในคำอธิบายที่ดีที่สุดที่ฉันได้รับ .. ขอบคุณสำหรับการแบ่งปัน ... : D
RohitS

1
นั่นคือ eyeopener
HebleV

11

อย่าคิดว่ากระบวนการโฮสต์จะเป็นแบบเธรดเดียวไม่ใช่ เธรดเดียวคืออะไรคือส่วนของกระบวนการโฮสต์ที่รันโค้ดจาวาสคริปต์ของคุณ

ยกเว้นคนทำงานเบื้องหลังแต่สิ่งเหล่านี้ทำให้สถานการณ์ซับซ้อน ...

ดังนั้นโค้ด js ทั้งหมดของคุณจึงทำงานในเธรดเดียวกันและไม่มีความเป็นไปได้ที่คุณจะได้รับโค้ด js สองส่วนที่แตกต่างกันเพื่อทำงานพร้อมกัน (ดังนั้นคุณจะไม่ต้องจัดการปัญหาที่เกิดขึ้นพร้อมกัน)

รหัส js ที่กำลังดำเนินการเป็นรหัสสุดท้ายที่กระบวนการโฮสต์หยิบขึ้นมาจากลูปเหตุการณ์ ในโค้ดของคุณโดยพื้นฐานแล้วคุณสามารถทำสองสิ่ง: เรียกใช้คำสั่งแบบซิงโครนัสและกำหนดเวลาฟังก์ชันที่จะดำเนินการในอนาคตเมื่อมีเหตุการณ์บางอย่างเกิดขึ้น

นี่คือการแสดงความคิดของฉัน (ระวัง: เป็นเพียงแค่นั้นฉันไม่รู้รายละเอียดการใช้งานเบราว์เซอร์!) ของโค้ดตัวอย่างของคุณ:

console.clear();                                   //exec sync
console.log("a");                                  //exec sync
setTimeout(                //schedule inAWhile to be executed at now +1 s 
    function inAWhile(){
        console.log("b");
    },1000);    
console.log("c");                                  //exec sync
setTimeout(
    function justNow(){          //schedule justNow to be executed just now
        console.log("d");
},0);       

ในขณะที่โค้ดของคุณกำลังทำงานอยู่เธรดอื่นในกระบวนการโฮสต์จะติดตามเหตุการณ์ของระบบทั้งหมดที่เกิดขึ้น (คลิกบน UI, อ่านไฟล์, แพ็กเก็ตเครือข่ายที่ได้รับเป็นต้น)

เมื่อโค้ดของคุณเสร็จสมบูรณ์โค้ดจะถูกลบออกจากลูปเหตุการณ์และกระบวนการโฮสต์จะกลับไปตรวจสอบโค้ดเพื่อดูว่ามีโค้ดให้รันอีกหรือไม่ ลูปเหตุการณ์มีตัวจัดการเหตุการณ์มากกว่าสองตัว: หนึ่งตัวดำเนินการตอนนี้ (ฟังก์ชัน justNow) และอีกตัวภายในหนึ่งวินาที (ฟังก์ชัน inAWhile)

ตอนนี้กระบวนการโฮสต์พยายามจับคู่เหตุการณ์ทั้งหมดที่เกิดขึ้นเพื่อดูว่ามีตัวจัดการที่ลงทะเบียนไว้หรือไม่ พบว่าเหตุการณ์ที่ JustNow รอคอยเกิดขึ้นจึงเริ่มรันโค้ดของมัน เมื่อออกจากฟังก์ชั่น justNow มันจะตรวจสอบการวนซ้ำของเหตุการณ์อีกครั้งค้นหาตัวจัดการเหตุการณ์ สมมติว่า 1 วินาทีผ่านไปมันจะเรียกใช้ฟังก์ชัน inAWhile และอื่น ๆ ....


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