Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เชี่ยวชาญ Ruby Threads: คู่มือ Multithreading สำหรับมือใหม่

Thread ใน Ruby คืออะไร? Threads ทำให้ Ruby programs ทำหลายอย่างพร้อมกัน

อย่างเช่น:

  • อ่านหลายไฟล์
  • จัดการหลาย web requests
  • ทำหลาย API connections

ผลคือ multi-threaded Ruby program ทำงานเสร็จเร็วขึ้น

แต่วันดีมีคำเตือน... ใน MRI (Matz's Ruby Interpreter) default way รัน Ruby apps คุณได้ประโยชน์จาก threads เฉพาะตอนรัน i/o bound applications

Limitation นี้เพราะ GIL (Global Interpreter Lock) Ruby interpreters ทางเลือกอย่าง JRuby หรือ Rubinius ใช้ multi-threading เต็มที่

เชี่ยวชาญ Ruby Threads: คู่มือ Multithreading สำหรับมือใหม่

Threads คืออะไร? Threads คือ workers หรือ units of execution ทุก process มีอย่างน้อย 1 thread สร้างเพิ่มได้ on demand

รู้อยากดู code example แต่ก่อนต้องพูด difference CPU bound และ I/O bound applications

I/O Bound Applications

I/O bound app คือ app ที่ต้องรอ external resource:

  • API request
  • database (query results)
  • disk read

Thread ตัดสินใจหยุดรอ resource available thread อื่นจึงรันทำงาน ไม่เสียเวลารอ ตัวอย่าง I/O bound app คือ web crawler ทุก request crawler ต้องรอ server respond ไม่ทำอะไรได้ขณะรอ แต่ถ้าใช้ threads... ทำ 4 requests พร้อมกัน handle responses ตามที่กลับมา fetch pages เร็วขึ้น

ถึงเวลา code example!

Creating Ruby Threads

สร้าง Ruby thread ใหม่ด้วย Thread.new ส่ง block พร้อมโค้ดที่ thread ต้องรัน

Thread.new { puts "hello from thread" }

ง่ายมาก แต่... โค้ดนี้ไม่มี output จาก thread:

t = Thread.new { puts 10**10 }
puts "hello"

ปัญหา: Ruby ไม่รอ threads เสร็จ เรียก join method บน thread เพื่อ fix:

t = Thread.new { puts 10**10 }
puts "hello"
t.join

อยากสร้าง multiple threads ใส่ array เรียก join ทุก thread:

threads = []
10.times { threads << Thread.new { puts 1 } }
threads.each(&:join)

ระหว่างสำรวจ Ruby threads documentation มีประโยชน์: https://ruby-doc.org/core-2.5.0/Thread.html

Threads และ Exceptions

ถ้า exception เกิดใน thread มันจะตายเงียบๆ โดยไม่หยุดโปรแกรมหรือแสดง error

ตัวอย่าง:

Thread.new { raise 'hell' }

เพื่อ debugging อยากให้โปรแกรมหยุดตอนเกิดเรื่องเลว ตั้ง flag นี้บน Thread เป็น true:

Thread.abort_on_exception = true

ตั้ง flag นี้ก่อนสร้าง threads นะ 🙂

Thread Pools

สมมติมี hundreds of items process สร้าง thread แต่ละอันจะทำลาย system resources

长得像这样:

pages_to_crawl = %w( index about contact ... )
pages_to_crawl.each do |page|
  Thread.new { puts page }
end

ทำแบบนี้ launch hundreds of connections ต่อ server ไม่อาจดี ตัวเลือก: thread pool

Thread pools ให้คุณควบคุมจำนวน active threads ได้ตลอดเวลา สร้าง pool เองได้ แต่ไม่แนะนำ ตัวอย่างใช้ celluloid gem

Note: Celluloid ตอนนี้ unmaintained แต่วิธี worker pools ยังใช้ได้

require 'celluloid'
class Worker
  include Celluloid
  def process_page(url)
    puts url
  end
end
pages_to_crawl = %w( index about contact products ... )
worker_pool = Worker.pool(size: 5)
# ถ้าต้องเก็บ return values เช็ค 'futures'
pages_to_crawl.each do |page|
  worker_pool.process_page(page)
end

คราวนี้แค่ 5 threads รัน และจบแล้วหยิบ item ถัดไป

Race Conditions และ Hazards อื่นๆ

ดูเก๋แต่ว่าก่อนไป灑 threads ทั่วโค้ด ต้องรู้ปัญหา concurrent code ตัวอย่าง threads prone to race conditions

Race condition คือ สิ่งเกิดขึ้น out of order ทำลายเละ อีกปัญหา: deadlock thread หนึ่ง hold exclusive access (mutex) resource และไม่ release ทำให้ inaccessible ให้ threads อื่น

หลีกเลี่ยง: หลีกเลี่ยง raw threads ใช้ gem ที่จัดการรายละเอียดให้

Threading gems เพิ่มเติม

ใช้ celluloid thread pool แต่มี concurrency gems อื่นๆ ควรเช็ค:

  • https://github.com/grosser/parallel
  • https://github.com/chadrem/workers
  • https://github.com/ruby-concurrency/concurrent-ruby

อkej นี่ แหว่งเรียนรู้ 1-2 เรื่อง Ruby threads!

ถ้าบทความมีประโยชน์ แชร์ ให้เพื่อนๆ เรียนรู้ด้วย 🙂