อธิบาย GVL
เหตุใดเธรดจึงมีข้อจำกัด
อธิบาย GVL เป็นบทเรียน Ruby Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Ruby Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Ruby Academy มีบทเรียนทั้งหมด 4 บทเรียน
GVL คืออะไร
GVL (ล็อก VM ส่วนกลาง หรือเดิมเรียกว่า GIL) คือล็อกภายใน CRuby ซึ่งเป็นตัวแปลภาษา Ruby มาตรฐาน
- อนุญาตให้มีเธรดเพียงหนึ่งตัวทำงานกับโค้ด Ruby ในแต่ละครั้ง
- ปกป้องส่วนทำงานภายในของตัวแปลภาษาจากความเสียหายที่เกิดจากการทำงานพร้อมกัน
- หมายความว่างานบน CPU ที่เขียนด้วย Ruby ล้วนจะไม่ทำงานแบบขนานอย่างแท้จริงบนหลายคอร์
การทำความเข้าใจ GVL จะช่วยอธิบายได้ว่าเธรดช่วยเมื่อใดและไม่ช่วยเมื่อใด
เหตุใดจึงมีสิ่งนี้
ส่วนทำงานภายในของ CRuby (การจัดสรรออบเจ็กต์ การเก็บขยะ และส่วนขยาย C) ไม่ได้ถูกเขียนให้ปลอดภัยต่อเธรดทั้งหมด GVL ทำให้ส่วนเหล่านี้ปลอดภัยด้วยการจัดลำดับการทำงานของ Ruby ให้เกิดขึ้นทีละลำดับ
การนำ GVL ออกโดยไม่ทำให้ส่วนอื่นเสียหายนั้นทำได้ยาก CRuby จึงยังคงใช้ GVL ต่อไป ส่วน JRuby และ TruffleRuby ไม่มี GVL และสามารถให้เธรดทำงานบนหลายคอร์ได้
งานที่ผูกกับ CPU
สำหรับงานที่ผูกกับ CPU (การคำนวณ การเรียงลำดับ การแยกวิเคราะห์) เธรดไม่ได้ทำให้งานเร็วขึ้นเมื่อใช้ CRuby เพราะในแต่ละขณะจะมีเธรดเพียงตัวเดียวที่ทำงานกับโค้ด Ruby ดังนั้นการใช้เธรด CPU สองตัวจึงให้ผลใกล้เคียงกับเธรดเดียวบวกภาระส่วนเกิน
def heavy
sum = 0
1_000_00.times { |i| sum += i }
sum
end
puts heavy
puts "CPU work does not parallelize under the GVL"การรับส่งข้อมูลปล่อย GVL
ประเด็นสำคัญคือ การรับส่งข้อมูลแบบบล็อกจะปล่อย GVL เมื่อเธรดหยุดพัก อ่านไฟล์ หรือรอซ็อกเก็ต เธรดนั้นจะปล่อยล็อกเพื่อให้เธรดอื่นทำงานกับ Ruby ได้
นี่คือเหตุผลที่เธรดเหมาะอย่างยิ่งกับงานที่ผูกกับการรับส่งข้อมูล แม้จะมี GVL อยู่ก็ตาม
การรอที่คาบเกี่ยวกัน
ในตัวอย่างนี้เธรดสามตัวต่างก็ทำ sleep เนื่องจาก sleep ปล่อย GVL การรอจึงคาบเกี่ยวกัน เวลารวมอยู่ที่ประมาณ 0.2s ไม่ใช่ 0.6s
start = Time.now
threads = (1..3).map do
Thread.new { sleep 0.2 }
end
threads.each(&:join)
elapsed = Time.now - start
puts "Elapsed roughly: " + elapsed.round(1).to_sส่วนขยาย C สามารถปล่อย GVL ได้
ส่วนขยาย C ที่เขียนมาอย่างดีสามารถปล่อย GVL ระหว่างการคำนวณระดับระบบที่เป็น heavy ซึ่งใช้เวลานานได้ โดยใช้ rb_thread_call_without_gvl วิธีนี้ทำให้งานระดับระบบทำงานแบบขนาน ขณะที่เธรด Ruby ยังคงทำงานต่อได้
ดังนั้นไลบรารีบางตัวจึงทำงานแบบขนานได้จริงแม้จะใช้ CRuby
เธรดยังคงสลับการทำงาน
แม้เป็นงาน CPU แต่ GVL ก็ไม่ได้หยุดเธรดอื่นไว้ตลอดไป CRuby จะสลับเธรดเป็นระยะ เพื่อให้แต่ละเธรดได้ทำงานตามลำดับ งานจึงเป็นแบบสอดประสาน (สลับกันทำ) แต่ไม่ใช่แบบขนาน (ทำพร้อมกันในเวลาเดียวกัน)
results = []
mutex = Mutex.new
threads = (1..3).map do |n|
Thread.new do
mutex.synchronize { results << n }
end
end
threads.each(&:join)
puts "Collected: " + results.sort.join(", ")การทำงานสอดประสานกับการทำงานแบบขนาน
คำสำคัญสองคำมีดังนี้
- การทำงานสอดประสาน: งานคืบหน้าโดยสลับกันทำ GVL อนุญาตให้ทำเช่นนี้ได้
- การทำงานแบบขนาน: งานทำงานในเวลาเดียวกันอย่างแท้จริงบนคอร์คนละตัว GVL ป้องกันสิ่งนี้สำหรับโค้ด Ruby
เธรดมอบการทำงานสอดประสาน ส่วนแร็กเตอร์มอบการทำงานแบบขนาน
การหลีกเลี่ยงข้อจำกัดของ GVL
หากต้องการใช้หลายคอร์สำหรับงาน CPU ใน CRuby คุณสามารถทำได้ดังนี้
- ใช้หลายโพรเซส (แต่ละโพรเซสมี GVL ของตัวเอง)
- ใช้แร็กเตอร์เพื่อทำงานแบบขนานภายในโพรเซสเดียว
- ส่งงานไปยังส่วนขยาย C ที่ปล่อย GVL
สำหรับงานรับส่งข้อมูล เธรดทั่วไปมักเพียงพอแล้ว
โพรเซสไม่มี GVL ร่วมกัน
โพรเซส OS แต่ละตัวทำงานด้วย Ruby VM ของตัวเองและมี GVL ของตัวเอง การแยกโพรเซสทำให้เกิดการทำงานแบบขนานอย่างแท้จริง แต่โพรเซสไม่ใช้หน่วยความจำร่วมกันและใช้ทรัพยากรมากกว่า
puts "Main pid: " + Process.pid.to_s
puts "Each forked process has its own independent GVL"การเลือกแนวทาง
แนวทางการตัดสินใจมีดังนี้
- มีเวลารอมากใช่หรือไม่ ใช้เธรด
- งาน CPU หนัก (heavy) และต้องการใช้หลายคอร์ใช่หรือไม่ ใช้โพรเซสหรือแร็กเตอร์
- ต้องใช้สถานะร่วมกันใช่หรือไม่ ใช้เธรดกับ
Mutexแต่คำนึงถึงข้อจำกัดของ GVL
การรู้จัก GVL จะช่วยป้องกันข้อผิดพลาดคลาสสิกที่คาดหวังให้เธรด CPU เพิ่มประสิทธิภาพได้เป็นเส้นตรง
ตรวจสอบความเข้าใจอย่างรวดเร็ว
ทดสอบความเข้าใจของคุณเกี่ยวกับ GVL
ทบทวน: GVL
ประเด็นสำคัญเกี่ยวกับล็อก VM ส่วนกลางมีดังนี้
- ใน CRuby มีเธรดเพียงตัวเดียวที่ทำงานกับโค้ด Ruby ได้ในแต่ละครั้ง
- GVL ปกป้องส่วนทำงานภายในของตัวแปลภาษา
- การรับส่งข้อมูลแบบบล็อกจะปล่อย GVL ทำให้การรอคาบเกี่ยวกันได้
- งาน Ruby ที่ผูกกับ CPU ไม่สามารถทำงานแบบขนานบนหลายคอร์ได้
- ใช้โพรเซสหรือแร็กเตอร์เพื่อให้เกิดการทำงานแบบขนานอย่างแท้จริง
ต่อไปเราจะดูไฟเบอร์สำหรับการทำงานสอดประสานแบบร่วมมือที่ใช้ทรัพยากรน้อย
คำถามที่พบบ่อย
บทเรียน “อธิบาย GVL” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “อธิบาย GVL” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Ruby Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Ruby Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “อธิบาย GVL”
เหตุใดเธรดจึงมีข้อจำกัด คุณปฏิบัติ Ruby Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Ruby Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Ruby Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “อธิบาย GVL” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Ruby Academy นี้ได้ไหม
ได้ บทเรียน Ruby Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ