07/09/2018, 15:56

THREADING IN RAILS

Bài viết này được thực hiện từ năm 2012 với Ruby 1.9 và AR 3.x. Trên Ruby 2.x trở đi, Threadsafe là mặc định nên sẽ có một vài thông tin đã trở nên không cần thiết. Multi-threaded Rails Thread (1) hiểu đơn giản là 1 tiến trình hoàn thiện rất nhỏ được CPU thực hiện trong 1 khoảng thời gian. ...

Bài viết này được thực hiện từ năm 2012 với Ruby 1.9 và AR 3.x. Trên Ruby 2.x trở đi, Threadsafe là mặc định nên sẽ có một vài thông tin đã trở nên không cần thiết.

Multi-threaded Rails

Thread (1) hiểu đơn giản là 1 tiến trình hoàn thiện rất nhỏ được CPU thực hiện trong 1 khoảng thời gian. Multi-threading là thuật ngữ chỉ việc xử lý đồng thời nhiều thread của CPU. Việc này làm tăng tốc độ xử lý, đồng thời tận dụng được hết khả năng vật lý thực tế của hệ thống.
Trong Ruby on Rails, thread mang 1 tính chất quan trọng đặc thù: chỉ có 1 thread duy nhất thực hiện trong 1 khoảng thời gian dù trên thực tế, request tới Rails hoàn toàn có thể hội tụ đồng thời nhiều thread. Điều này được xử lý bởi trình thông dịch GIL ( Global Interpreter Lock )(4)
GIL
「illustrated in this diagram from igvita.com」

Config và sử dụng thread cho Rails

  1. Threadsafe

Xử lý thread quan trọng vẫn là đảm bảo cho các thread không bị kẹt, hay còn gọi là thread safe.
Bắt đầu từ ActiveRecord 2.x việc đảm bảo thread safe sẽ được AR điều khiển.
Để enable thread safe ta thêm 1 dòng lệnh vào

# config/environments/production.rb
config.threadsafe!

thread safe
Ruby threadsafe(7)
「 illustrated in this diagram from Yehuda Katz」

Muốn chắc chắn threadsafe đã hoạt động, ta có thể tạo 1 action foo()(2):

    def foo
        n = params[:n].to_i
        sleep n
        render text: "I should have taken #{n} seconds!"
    end

Mở trình duyệt W1 và truyền cho ?n=15, sau đó nhanh chóng mở thêm 1 trình duyệt W2 nữa và truyền ?n=2. W1 sẽ hiển thị text sau 15s, và sau đó 2s W2 mới hiển thị. Như vậy threadsafe đã hoạt động.

  1. Multi Threading

Sau khi enable threadsafe, chúng ta cần setup thêm preload vì khi active thread trên AR 3.2 thì chế độ automatic loading(6) của ActiveSupport::Dependencies đã bị tắt đi. Để làm được điều đó chúng ta chỉ cần thêm dòng sau

# config/environments/production.rb
config.eager_load_paths << "#{RAILS_ROOT}/lib"

Như vậy, multi-threading đã sẵn sàng cho Rails.
Nhằm giải thích về sự khác nhau giữa Rails thông thường và multithreaded Rails, chúng ta sẽ khởi tạo 2 hàm f1() và f2() trong rails console:

    def f1
        i = 0
        while i <= 2
            puts "F1 loop #{i+1} start at #{Time.now}"
            sleep 2
            puts "F1 loop #{i+1} end at #{Time.now}"
            i = i + 1
        end
    end

    def f2
        j = 0
        while j <= 2
            puts "F2 loop #{j+1} start at #{Time.now}"
            sleep 2
            puts "F2 loop #{j+1} end at #{Time.now}"
            j = j + 1
        end
    end

Khi dùng Multi-thread, ta sử dụng Thread.new để khởi tạo 1 thread mới, và f1(),f2() sẽ được thực hiện trong những thread đó. Sau khi f1() và f2() kết thúc cần sử dụng join để đóng 2 thread trên trả tài nguyên lại cho thread chính

    def multithreaded
        time_start = Time.now
        puts "Started at #{time_start}"
        t1 = Thread.new{ f1() }
        t2 = Thread.new{ f2() }
        t1.join
        t2.join
        time_end = Time.now
        puts "End at #{time_end}"
        puts "Total time taken: #{time_end.sec - time_start.sec} second(s)"
    end

Và kết quả:

Started at 2012-09-28 10:48:59 +0700
F1 loop 1 start at 2012-09-28 10:48:59 +0700
F2 loop 1 start at 2012-09-28 10:48:59 +0700
F1 loop 1 end at 2012-09-28 10:49:01 +0700
F2 loop 1 end at 2012-09-28 10:49:01 +0700
F2 loop 2 start at 2012-09-28 10:49:01 +0700
F1 loop 2 start at 2012-09-28 10:49:01 +0700
F1 loop 2 end at 2012-09-28 10:49:03 +0700
F1 loop 3 start at 2012-09-28 10:49:03 +0700
F2 loop 2 end at 2012-09-28 10:49:03 +0700
F2 loop 3 start at 2012-09-28 10:49:03 +0700
F1 loop 3 end at 2012-09-28 10:49:05 +0700
F2 loop 3 end at 2012-09-28 10:49:05 +0700
End at 2012-09-28 10:49:05 +0700
Total time taken: 6 second(s)

Còn khi không sử dụng Multi thread:

    def nonthreaded
        time_start = Time.now
        puts "Started at #{time_start}"
        t1 = f1()
        t2 = f2()
        time_end = Time.now
        puts "End at #{time_end}"
        puts "Total time taken: #{time_end.sec - time_start.sec} second(s)"
    end

kết quả nhận được

Started at 2012-09-28 10:51:20 +0700
F1 loop 1 start at 2012-09-28 10:51:20 +0700
F1 loop 1 end at 2012-09-28 10:51:22 +0700
F1 loop 2 start at 2012-09-28 10:51:22 +0700
F1 loop 2 end at 2012-09-28 10:51:24 +0700
F1 loop 3 start at 2012-09-28 10:51:24 +0700
F1 loop 3 end at 2012-09-28 10:51:26 +0700
F2 loop 1 start at 2012-09-28 10:51:26 +0700
F2 loop 1 end at 2012-09-28 10:51:28 +0700
F2 loop 2 start at 2012-09-28 10:51:28 +0700
F2 loop 2 end at 2012-09-28 10:51:30 +0700
F2 loop 3 start at 2012-09-28 10:51:30 +0700
F2 loop 3 end at 2012-09-28 10:51:32 +0700
End at 2012-09-28 10:51:32 +0700
Total time taken: 12 second(s)

So sánh 2 kết quả ta có thể thấy rằng khi multithread thì f1() và f2() chạy đồng thời song song còn khi không dùng, f1() chạy cho đến khi kết thúc rồi f2() mới bắt đầu, và đương nhiên multithreaded tốn ít thời gian hơn để xử lý 2 hàm đó.

Bạn có thể xem tại ĐÂY

Một số điều cần biết về multi-threaded rails hiện tại(3)

Bắt đầu từ AR 2.x multi-thread mới bắt đầu được hỗ trợ, và cho tới hiện tại ( AR 3.2 ) vẫn chưa hoàn thiện. Vì thế nó vẫn chưa thể hiện được khả năng vượt trội của mình so với GIL. Multi-threading chỉ nên sử dụng khi

  • data unshared : Chỉ sử dụng với các dữ liệu không share như khi đăng nhập, đăng ký, tính toán nội hàm … còn những hoạt động có khả năng trao đổi thông tin thời gian thực như chat, đặt hàng online thì fork hiện tại an toàn hơn
  • Supported server : các server Ruby on Rails hỗ trợ multi-threaded Rails chưa có nhiều, chỉ có 1 vài server như Thin hoặc NGinx có khả năng.
  • JRuby : multi-threaded Rails hiện tại hoạt động tốt nhất trên JRuby bởi JRuby không sử dụng GIL làm trình thông dịch mà sử dụng JVM – một nền tảng hỗ trợ multi-thread tuyệt đối

Kết luận

Khả năng tận dụng tài nguyên hệ thống và tốc độ xử lý nhiều nhiệm vụ cùng lúc của multi-thread đã được chứng minh giá trị ở những ngôn ngữ lập trình khác như C, Java …và tương lai trong Ruby on Rails chắc chắn cũng sẽ là 1 kỹ thuật rất được quan tâm.
Tuy nhiên, Ruby là một ngôn ngữ lập trình tuổi đời còn rất trẻ, cộng với việc GIL vẫn đảm bảo cho Ruby hoạt động tốt, multi-threaded Rails vẫn chỉ nên sử dụng hạn chế cho tới khi ActiveRecord hỗ trợ multithread tốt hơn.

Tham khảo

  1. http://en.wikipedia.org
  2. http://www.jordanhollinger.com
  3. http://www.unlimitednovelty.com
  4. http://www.igvita.com
  5. http://bibwild.wordpress.com
  6. http://m.onkey.org
  7. http://yehudakatz.com
  8. http://blog.smartlogicsolutions.com
0