Event-loop แบบพื้นฐานมากกกกก ตอนที่ 2 Event-loop มาสักที!!!
2 March, 2019, By Tar Jarupong
หลังจากที่ผมอธิบาย concept แบบคร่าวๆไปในครั้งที่แล้ว วันนี้เราจะลงลึกไประดับนึงครับ มาเริ่มกันเลยดีกว่า แต่ก่อนอื่นเลยที่เราจะมาทำความเข้าใจเรื่อง Event-loop มาคุยเรื่อง Reactor pattern กันก่อนนะครับ
Reactor Pattern
มันคืออะไรกันนะ มันก็คือ pattern ตัวนึงที่เข้าใจจัดการ event-driven architecture โดยตัวมันเนี๊ยจะทำการ พูดง่ายๆก็คือ Reactor Pattern ทำหน้าที่จัดการ process ที่ใช้เวลานานๆไปเป็น asynchronouse ครับ แล้วส่งกลับมาเป็น callback อย่างที่เรารู้จักกันนี้เอง
โดยโครงสร้างของมันจะประกอบด้วย 4 ตัวหลักตามนี้ครับ
1. Resouce => ก็ตามชื่อเลยครับมันคือ resource for input & output
2. Synchronous Event Demultiplexer => มันกับตัวแจ้งเตือนว่าทำงานเกี่ยวกับ I/O เสร็จแล้วส่งต่อไปที่ Event queue
3. Event Queue => คือช่องที่เก็บ process ที่ต่อคิวกันรอกันอยู่
4. Dispatcher => คือเอาตัว process จาก Event queue มาทำงานแล้วส่งกลับไป
เรียบร้อยแล้วครับสำหรับพื้นฐาน ของ Reactor Pattern เรามาเข้าเรื่อง Event-loop แบบจริงจังกันครับ มาลุยกัน~~~
Event-loop
ในที่สุดก็เข้าเรื่องพี่ loop สักที ผมขอเริ่มต้นที่รูปภาพแรกก็เลยครับ
จะเห็นว่ามี 6 ขั้นตอนหลักๆ ผมจะเริ่มอธิบายเลยนะครับ เย้~
1. Application => จะทำการสร้าง request หรือ process เพื่อส่งต่อไปให้ Event Demultiplexer การทำงานในส่วนนี้เป็นแบบ Non-Blocking เพื่อทำงานส่งต่อไปที่ Event Demultiplexer
2. Event Demultiplxer => เป็นจุดที่ทำงาน เกี่ยวกับ I/O ล้วนๆ เช่น อ่านไฟล์ เขียนไฟล์ เป็นต้น แล้วส่งต่อไปที่ Event queue
3. Event queue => เป็นตัวเก็บ process ที่จะเรียกไปใช้งานผ่านเจ้า Even loop
4. Event loop => จะคอยเช็คไปเรื่อยๆว่ามี process ค้างอยู่ใน Event queue รึเปล่าแล้ว ถ้ามีก็เอามาทำงานให้มันเสร็จจะได้ส่งกลับไป
5. Handler => หลังจากที่ process นั้นๆทำงานเสร็จแล้วก็ส่งกลับมาว่า ฉันทำเสร็จแล้วน๊าาาา ไปทำ queue ต่อไปได้เลยเน้อ ในกรณีที่มี request ใหม่เข้ามาจะถูกถึงไปยัง ข้อ 1 แล้วทำตามขั้นตอนต่อไป
6. Event loop ก็จะวนกลับไปทำงานในข้อหนึ่งใหม่ แล้วก็ทำอย่างนี้ไปเรื่อยๆ
ในที่สุดผมก็เล่าเรื่อง Event loop เสร็จสักที แต่เดี๋ยวก่อนครับ การทำแบบนั้นมันคือแบบ Non-Blocking I/O แล้วถ้าเป็น Blocking I/O ล่ะจะทำยังไงดี มันก็จะเป็นสิ่งที่ผมพูดต่อจากนี้ครับ อย่าพึ่งเบื่อกันไปก่อนนะครับ จะจบแล้ว มาเริ่มเรื่องสุดท้ายของบทความนี้กัน
Thread pool
เรามาพูดต่อให้ละเอียดขึ้นดีกว่าครับด้วยภาพนี้เลยครับ
ที่จริงแล้วพี่ thread pool ของเราเอาไว้รับมือกับการทำงานที่เป็น blocking I/O ครับ โดยการทำงานของมันก็ง่ายแสนง่าย ก็คือมันจะทำการเช็คแต่ละ request มาเป็น Blocking I/O รึเปล่าถ้าเป็นก็ให้ส่ง request นั้นไปทำงานโดยมี thread pool เป็นตัวจัดการแทน event loop แต่เอ๊ะทำไมเรายังต้องใช้ thread pool อยู่แค่ event loop ก็น่าจะจัดการได้แล้วนี้น๊า แต่...มันผิดเลยครับเพราะตัว event loop จะ process อะไรที่ทำงานแป๊บเดียวก็เสร็จไม่เสียนานมากๆ แต่ถ้ามี request ที่ต้องใช้เวลานานมากๆล่ะอย่างเช่น การทำงานกับ file system หรือติดต่อ database ที่ใช้เวลานานๆ ตัว event loop เริ่มจะเป็นคอขวดแล้วใช่มั๊ยล่ะครับ นี้ถึงเป็นเหตุผลทำไมถึงมี thread pool และทำไม database driver ที่เราชอบใช้กันมันถึงเป็น callback,promise ครับ การทำงานของมันก็ตามรูปที่ผมวาดไว้เลยครับโดนการทำงานก็คือเช็ค request ว่าเป็น Blocking I/O รึเปล่าถ้าเป็นก็ไปเช็คว่าข้างในมี thread pool เหลือให้ใช้รึเปล่าโดยปรกติแล้วจะมีแค่ 4 ตัวเท่านั้นแต่เราสามารถเพิ่มได้โดยใช้คำสั่ง UV_THREADPOOL_SIZE=5 ได้เลยครับ จะได้เพิ่มช่องสำหรับจัดการกับ Blocking I/O ที่จะเกิดขึ้นครับ
เสริม
พยายามหลีกเลี่ยงการใช้งาน function ที่เป็นแบบ Blocking I/O ให้ได้ครับเพราะว่าจะไปทำงานอยู่บน thread pool มากเกินไปอย่างเช่น
fs.fsyncSync(fd)
fs.lchmodSync(path, mode)
ก็จบกันแล้วนะครับสำหรับ Event loop ตอนสุดท้ายแบบพื้นฐานมากๆนะครับ ผมเกือบลืมเลย ปุกาศๆ ถ้าเราอีกสักหน่อยไม่น่าจะเกิน 10 ปีนี้ เดี๋ยวๆไม่ใช่สิ ไม่เกิน 2 เดือนข้างหน้าพวกเราอยากจะทำคลิปสอน NodeJS แบบฟรีๆ ไม่กั๊กอะไรทั้งนั้นแต่ถ้าใครบริจาคก็ได้ครับ แต่ที่สำคัญคือฟรี!!!!
Ref: