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 เต็มที่

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!
ถ้าบทความมีประโยชน์ แชร์ ให้เพื่อนๆ เรียนรู้ด้วย 🙂