Thread trong Ruby là gì?
Threads làm chương trình Ruby làm nhiều việc cùng lúc.
Ví dụ:
- Đọc nhiều tệp
- Xử lý nhiều web request
- Tạo nhiều API connection
Kết quả chương trình Ruby đa luồng (multi-threaded), hoàn thành việc nhanh hơn.
Nhưng một cảnh báo…
Trong MRI (Matz's Ruby Interpreter), cách mặc định chạy Ruby, chỉ có lợi từ threads khi chạy ứng dụng I/O bound.
Hạn chế này do GIL (Global Interpreter Lock).
Trình thông dịch Ruby thay thế như JRuby hoặc Rubinius tận dụng đa luồng hoàn toàn.

Vậy thread là gì?
Threads là worker, hoặc đơn vị thực thi.
Mọi process có ít nhất một thread & bạn tạo thêm theo nhu cầu.
Biết bạn muốn xem ví dụ code.
Nhưng trước, nói về khác biệt CPU bound & I/O bound.
Ứng Dụng I/O Bound
I/O bound app là app cần chờ tài nguyên bên ngoài:
- API request
- Database (kết quả query)
- Đọc đĩa
Thread quyết định dừng chờ tài nguyên khả dụng. Nghĩa là thread khác chạy làm việc không lãng phí thời gian chờ.
Ví dụ I/O bound app là web crawler.
Mỗi request, crawler chờ server phản hồi, không làm gì khi chờ.
Nhưng dùng threads…
Có thể tạo 4 request cùng lúc & xử lý phản hồi khi về, lấy trang nhanh hơn.
Bây giờ đến ví dụ code.
Tạo Ruby Threads
Tạo thread mới Ruby gọi Thread.new.
Đảm bảo truyền block code thread cần chạy.
Thread.new { puts "hello from thread" }
Rất dễ, đúng?
Tuy nhiên.
Nếu code sau bạn thấy không có đầu ra từ thread:
t = Thread.new { puts 10**10 }
puts "hello"
Vấn đề Ruby không chờ thread kết thúc.
Cần gọi join trên thread sửa code:
t = Thread.new { puts 10**10 }
puts "hello"
t.join
Muốn tạo nhiều thread, cho vào mảng & gọi join mỗi thread.
Ví dụ:
threads = []
10.times { threads << Thread.new { puts 1 } }
threads.each(&:join)
Trong khám phá Ruby threads có thể tìm tài liệu hữu ích:
https://ruby-doc.org/core-2.5.0/Thread.html
Threads Và Exceptions
Nếu exception xảy ra bên trong thread nó chết thầm không dừng chương trình hoặc hiện thông báo lỗi.
Ví dụ:
Thread.new { raise 'hell' }
Mục đích debug, muốn chương trình dừng khi chuyện xấu xảy ra. Làm bằng cách set cờ sau trên Thread thành true:
Thread.abort_on_exception = true
Đảm bảo set cờ này trước tạo threads 🙂
Thread Pools
Giả sử có trăm mục xử lý, khởi động thread cho từng sẽ phá hoại tài nguyên hệ thống.
Trông như:
pages_to_crawl = %w( index about contact ... )
pages_to_crawl.each do |page|
Thread.new { puts page }
end
Làm thế sẽ launch trăm connection lên server, không hay.
Giải pháp dùng thread pool.
Thread pools cho phép kiểm soát số thread hoạt động tại thời điểm.
Có thể tự xây pool, nhưng không khuyến khích. Ví dụ sau dùng gem celluloid làm giúp.
Lưu ý: Celluloid không còn maintain, nhưng ý tưởng worker pool vẫn áp dụng.
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)
# Cần thu thập giá trị trả về xem 'futures'
pages_to_crawl.each do |page|
worker_pool.process_page(page)
end
Lần này chỉ 5 thread chạy, khi xong lấy mục tiếp theo.
Race Conditions Và Nguy Cơ Khác
Nghe cool nhưng trước khi rải threads khắp code phải biết vấn đề liên quan code concurrent.
Ví dụ, threads dễ gặp race conditions.
Race condition là khi thứ xảy ra sai thứ tự gây lộn xộn.
Vấn đề khác deadlock. Khi thread nắm quyền độc quyền (dùng locking như mutex) tài nguyên và không bao giờ giải phóng, làm không truy cập được cho thread khác.
Tránh vấn đề, tốt nhất tránh raw threads và dùng gem đã lo chi tiết.
Gem Threading Khác
Đã dùng celluloid cho thread pool, nhưng nhiều gem tập trung concurrency khác cần xem:
- https://github.com/grosser/parallel
- https://github.com/chadrem/workers
- https://github.com/ruby-concurrency/concurrent-ruby
Ok đó, hy vọng bạn học được điều hoặc hai về Ruby threads!
Nếu thấy hữu ích xin chia sẻ với bạn bè để họ cũng học 🙂