การเรียนรู้พร้อมกัน
ผู้ใช้หลายคนจะใช้แอปของคุณพร้อมกัน และคุณต้องการส่งแอปของคุณให้เร็วที่สุด ดังนั้น คุณจะต้องมีวิธีจัดการกับภาวะพร้อมกัน อย่ากลัว! เว็บเซิร์ฟเวอร์ส่วนใหญ่ทำเช่นนี้โดยค่าเริ่มต้นแล้ว แต่เมื่อคุณต้องการปรับขนาด คุณต้องการใช้การทำงานพร้อมกันอย่างมีประสิทธิภาพสูงสุด
การทำงานพร้อมกันประเภทต่างๆ
มีหลายวิธีในการจัดการภาวะพร้อมกัน:หลายกระบวนการ มัลติเธรด และตัวขับเคลื่อนเหตุการณ์ สิ่งเหล่านี้มีการใช้งาน ข้อดีและข้อเสีย ในบทความนี้ คุณจะได้เรียนรู้ความแตกต่างและเมื่อต้องใช้
หลายขั้นตอน (ยูนิคอร์น)
นี่เป็นวิธีที่ง่ายที่สุดในการจัดการภาวะพร้อมกัน กระบวนการหลักแยกตัวเองไปยังกระบวนการของผู้ปฏิบัติงานหลายคน กระบวนการของผู้ปฏิบัติงานจัดการคำขอจริง ในขณะที่ต้นแบบจัดการผู้ปฏิบัติงาน
กระบวนการของผู้ปฏิบัติงานแต่ละคนมีฐานรหัสเต็มในหน่วยความจำ วิธีนี้ทำให้วิธีนี้ใช้หน่วยความจำค่อนข้างมาก และทำให้ปรับขนาดเป็นโครงสร้างพื้นฐานขนาดใหญ่ได้ยาก
สรุปหลายกระบวนการ | |
---|---|
กรณีการใช้งาน | ตัวอย่างหนึ่งที่ไม่ใช่ทับทิมที่คุณอาจทราบคือเบราว์เซอร์ Chrome ใช้กระบวนการทำงานพร้อมกันหลายกระบวนการเพื่อให้แต่ละแท็บมีกระบวนการของตนเอง อนุญาตให้แท็บเดียวหยุดทำงานโดยไม่ต้องปิดแอปพลิเคชันทั้งหมด ในกรณีดังกล่าว ยังช่วยแยกการหาประโยชน์จากแท็บเดียวได้ |
ข้อดี | ใช้งานง่ายที่สุด ละเว้นปัญหาด้านความปลอดภัยของเธรด ผู้ปฏิบัติงานแต่ละคนสามารถหยุดทำงานโดยไม่ทำให้ระบบส่วนที่เหลือเสียหาย |
ข้อเสีย | แต่ละกระบวนการจะโหลด codebase แบบเต็มในหน่วยความจำ ทำให้ต้องใช้หน่วยความจำมาก ดังนั้นจึงไม่ปรับขนาดเป็นการเชื่อมต่อพร้อมกันจำนวนมาก |
มัลติเธรด (Puma)
โมเดลการทำเธรดนี้อนุญาตให้กระบวนการเดียวจัดการกับคำขอหลายรายการพร้อมกัน โดยเรียกใช้หลายเธรดภายในกระบวนการเดียว
ต่างจากวิธีการแบบหลายกระบวนการ เธรดทั้งหมดจะทำงานภายในกระบวนการเดียวกัน ซึ่งหมายความว่าพวกเขาแบ่งปันข้อมูลเช่นตัวแปรส่วนกลาง ดังนั้น แต่ละเธรดจึงใช้หน่วยความจำเพิ่มเติมเพียงส่วนเล็กๆ เท่านั้น
ล็อกล่ามทั่วโลก
สิ่งนี้นำเราไปสู่การล็อคล่ามทั่วโลก (GIL) ใน MRI GIL เป็นตัวล็อคการทำงานของรหัส Ruby ทั้งหมด แม้ว่าชุดข้อความของเราจะทำงานพร้อมกัน แต่มีการใช้งานเพียงครั้งละหนึ่งชุดเท่านั้น
IO ทำงานนอก GIL เมื่อคุณเรียกใช้การสืบค้นฐานข้อมูลโดยรอให้ผลลัพธ์กลับมา จะไม่ล็อก กระทู้อื่นจะมีโอกาสทำงานบ้างในระหว่างนี้ หากคุณคำนวณและดำเนินการกับแฮชหรืออาร์เรย์ในเธรดเป็นจำนวนมาก คุณจะใช้คอร์เดียวเท่านั้นหากคุณใช้ MRI ในกรณีส่วนใหญ่ คุณยังต้องการกระบวนการหลายขั้นตอนเพื่อใช้งานเครื่องของคุณอย่างเต็มที่ หรือคุณอาจใช้ Rubinius หรือ jRuby ซึ่งไม่มี GIL
ความปลอดภัยของเธรด
หากคุณใช้หลายเธรด คุณจะต้องระมัดระวังในการเขียนโค้ดทั้งหมดที่จัดการข้อมูลที่แชร์ด้วยวิธีที่ปลอดภัยสำหรับเธรด คุณสามารถทำเช่นนี้ได้โดยใช้ Mutex เพื่อล็อกโครงสร้างข้อมูลที่แชร์ก่อนที่คุณจะจัดการ วิธีนี้จะช่วยให้แน่ใจว่าเธรดอื่นๆ ไม่ได้อ้างอิงงานของพวกเขาจากข้อมูลเก่าในขณะที่คุณกำลังเปลี่ยนแปลงข้อมูล
สรุปแบบมัลติเธรด | |
---|---|
กรณีการใช้งาน | นี่คือตัวเลือก "กลางถนน" ใช้สำหรับเว็บแอปพลิเคชันมาตรฐานจำนวนมากซึ่งควรจัดการกับคำขอสั้นๆ จำนวนมาก (เช่น เว็บแอปพลิเคชันที่ไม่ว่าง) |
ข้อดี | ใช้หน่วยความจำน้อยกว่าหลายกระบวนการ |
ข้อเสีย | คุณต้องตรวจสอบให้แน่ใจว่าโค้ดของคุณปลอดภัยสำหรับเธรด หากเธรดทำให้เกิดการขัดข้อง อาจทำให้กระบวนการของคุณหยุดชะงัก GIL จะล็อกการทำงานทั้งหมดยกเว้น I/O |
วนรอบเหตุการณ์ (บาง)
ลูปเหตุการณ์ถูกใช้เมื่อคุณต้องการดำเนินการ I/O พร้อมกันจำนวนมาก ตัวแบบเองไม่ได้บังคับให้ดำเนินการหลายคำขอพร้อมกัน แต่เป็นวิธีที่มีประสิทธิภาพในการจัดการผู้ใช้จำนวนมากพร้อมกันจำนวนมาก
ด้านล่างนี้ คุณจะเห็นการวนซ้ำเหตุการณ์ง่ายๆ ที่เขียนด้วย Ruby การวนซ้ำจะนำเหตุการณ์จาก event_queue
และจัดการกับมัน หากไม่มีกิจกรรมก็จะเข้าสู่โหมดสลีปและทำซ้ำเพื่อดูว่ามีกิจกรรมใหม่ในคิวหรือไม่
loop do
if event_queue.any?
handle_event(event_queue.pop)
else
sleep 0.1
end
end
แบบมีภาพประกอบ
ในภาพประกอบนี้ เรากำลังก้าวไปอีกขั้น ตอนนี้วนรอบกิจกรรมเต้นอย่างสวยงามด้วยระบบปฏิบัติการ คิว และหน่วยความจำบางส่วน
ทีละขั้นตอน
- ระบบปฏิบัติการจะติดตามความพร้อมใช้งานของเครือข่ายและดิสก์
- เมื่อ OS เห็นว่า I/O พร้อมแล้ว ก็จะส่งเหตุการณ์ไปที่คิว
- คิวคือรายการเหตุการณ์ที่วนรอบเหตุการณ์ขึ้นบนสุด
- วนรอบเหตุการณ์จัดการเหตุการณ์
- ใช้หน่วยความจำบางส่วนในการจัดเก็บข้อมูลเมตาเกี่ยวกับการเชื่อมต่อ
- มันสามารถส่งเหตุการณ์ใหม่โดยตรงไปยังคิวเหตุการณ์อีกครั้ง ตัวอย่างเช่น ข้อความเพื่อปิดคิวตามเนื้อหาของกิจกรรม
- หากต้องการดำเนินการ I/O ระบบจะแจ้ง OS ว่าสนใจในการดำเนินการ I/O ที่เฉพาะเจาะจง ระบบปฏิบัติการจะติดตามเครือข่ายและดิสก์ (ดู [1]) และเพิ่มเหตุการณ์อีกครั้งเมื่อ I/O พร้อมใช้งาน
สรุปการวนรอบเหตุการณ์ | |
---|---|
กรณีการใช้งาน | เมื่อใช้การเชื่อมต่อพร้อมกันจำนวนมากกับผู้ใช้ของคุณ คิดถึงบริการอย่าง Slack การแจ้งเตือนของ Chrome |
ข้อดี | เกือบไม่มีหน่วยความจำโอเวอร์เฮดต่อการเชื่อมต่อ ขยายเป็นการเชื่อมต่อแบบขนานจำนวนมาก |
ข้อเสีย | เป็นโมเดลทางจิตที่เข้าใจยาก ขนาดแบทช์ต้องเล็กและคาดเดาได้เพื่อหลีกเลี่ยงการสร้างคิว |
ควรใช้อันไหนดี?
เราหวังว่าบทความนี้จะช่วยให้คุณเข้าใจโมเดลการทำงานพร้อมกันต่างๆ ได้ดีขึ้น เป็นเรื่องที่ยากกว่าที่จะเข้าใจในฐานะนักพัฒนาซอฟต์แวร์ แต่การทำความเข้าใจหัวข้อดังกล่าวจะทำให้คุณมีเครื่องมือในการทดสอบและใช้การตั้งค่าที่เหมาะสมสำหรับแอปของคุณ
โดยสรุป
- สำหรับการทำเธรดของแอปส่วนใหญ่นั้นสมเหตุสมผล ดูเหมือนว่าระบบนิเวศของ Ruby/Rails จะ (ช้า) เคลื่อนไหวในลักษณะนี้
- หากคุณเรียกใช้แอปที่มีการทำงานพร้อมกันสูงกับสตรีมที่ใช้เวลานาน event-loop จะช่วยให้คุณปรับขนาดได้
- หากคุณไม่มีไซต์ที่มีการเข้าชมสูง หรือคุณคาดหวังให้พนักงานของคุณหยุดทำงาน ให้เลือกใช้หลายกระบวนการแบบเก่าที่ดี
และเป็นไปได้ที่จะเรียกใช้การวนซ้ำเหตุการณ์ ภายในเธรด ภายในการตั้งค่าแบบหลายกระบวนการ ใช่แล้ว คุณสามารถกินสตรอพวาเฟลและกินมันได้เช่นกัน!
หากคุณต้องการอ่านเพิ่มเติมเกี่ยวกับโมเดลการทำงานพร้อมกันเหล่านี้ โปรดอ่านบทความโดยละเอียดเกี่ยวกับลูปแบบหลายกระบวนการ มัลติเธรด และเหตุการณ์