2012-04-19 29 views
6

Khi lần đầu tiên tôi phát hiện ra các chủ đề, tôi đã thử kiểm tra xem chúng có thực sự hoạt động như mong đợi hay không bằng cách gọi ngủ trong nhiều chủ đề, so với việc gọi ngủ bình thường. Nó hoạt động, và tôi rất hạnh phúc.Tôi có thể sử dụng những gì cho các chủ đề Ruby, nếu chúng không thực sự song song?

Nhưng sau đó một người bạn của tôi nói với tôi rằng các chủ đề này không thực sự song song, và giấc ngủ đó phải giả mạo nó.

Vì vậy, bây giờ tôi đã viết bài kiểm tra này để làm một số chế biến thực:

class Test 
    ITERATIONS = 1000 

    def run_threads 
    start = Time.now 

    t1 = Thread.new do 
     do_iterations 
    end 

    t2 = Thread.new do 
     do_iterations 
    end 

    t3 = Thread.new do 
     do_iterations 
    end 

    t4 = Thread.new do 
     do_iterations 
    end 

    t1.join 
    t2.join 
    t3.join 
    t4.join 

    puts Time.now - start 
    end 

    def run_normal 
    start = Time.now 

    do_iterations 
    do_iterations 
    do_iterations 
    do_iterations 

    puts Time.now - start 
    end 

    def do_iterations 
    1.upto ITERATIONS do |i| 
     999.downto(1).inject(:*) # 999! 
    end 
    end 
end 

Và bây giờ tôi rất buồn, vì run_threads() không những không thực hiện tốt hơn so với run_normal, nó thậm chí còn chậm hơn!

Sau đó, tại sao tôi nên làm phức tạp ứng dụng của mình với chuỗi, nếu chúng không thực sự song song?

** CẬP NHẬT **

@ fl00r nói rằng tôi có thể tận dụng lợi thế của đề nếu tôi sử dụng chúng cho các nhiệm vụ IO, vì vậy tôi đã viết thêm hai biến thể của do_iterations:

def do_iterations 
    # filesystem IO 
    1.upto ITERATIONS do |i| 
    5.times do 
     # create file 
     content = "some content #{i}" 
     file_name = "#{Rails.root}/tmp/do-iterations-#{UUIDTools::UUID.timestamp_create.hexdigest}" 
     file = ::File.new file_name, 'w' 
     file.write content 
     file.close 

     # read and delete file 
     file = ::File.new file_name, 'r' 
     content = file.read 
     file.close 
     ::File.delete file_name 
    end 
    end 
end 

def do_iterations 
    # MongoDB IO (through MongoID) 
    1.upto ITERATIONS do |i| 
    TestModel.create! :name => "some-name-#{i}" 
    end 
    TestModel.delete_all 
end 

Các kết quả hiệu suất vẫn như cũ: bình thường> chủ đề.

Nhưng bây giờ tôi không chắc liệu máy ảo của tôi có thể sử dụng tất cả các lõi hay không. Sẽ trở lại khi tôi đã thử nghiệm điều đó.

+0

Chủ đề hút, Xơ đá! : D – fl00r

+0

Threading là giả nếu bạn sử dụng ruby ​​chính thức, nhưng đối với jruby và rubinius, tôi tin rằng đó là luồng thực tế. – texasbruce

+0

Hiệu suất không phải là lý do duy nhất để sử dụng đề tài. Xem câu trả lời của tôi. – Phrogz

Trả lời

7

Chỉ có thể nhanh hơn nếu bạn có IO chậm.

Trong Ruby bạn có Khóa thông dịch toàn cầu, do đó, chỉ một Chủ đề có thể hoạt động tại một thời điểm. Vì vậy, Ruby dành nhiều thời gian để quản lý Thread nào nên được kích hoạt tại một thời điểm (lập lịch trình luồng). Vì vậy, trong trường hợp của bạn, khi không có bất kỳ IO nó sẽ chậm hơn!

Bạn có thể sử dụng Rubinius hoặc JRuby để sử dụng Chủ đề thực.

Ví dụ với IO:

module Test 
    extend self 

    def run_threads(method) 
    start = Time.now 

    threads = [] 
    4.times do 
     threads << Thread.new{ send(method) } 
    end 

    threads.each(&:join) 

    puts Time.now - start 
    end 

    def run_forks(method) 
    start = Time.now 

    4.times do 
     fork do 
     send(method) 
     end 
    end 
    Process.waitall 

    puts Time.now - start 
    end 

    def run_normal(method) 
    start = Time.now 

    4.times{ send(method) } 

    puts Time.now - start 
    end 

    def do_io 
    system "sleep 1" 
    end 

    def do_non_io 
    1000.times do |i| 
     999.downto(1).inject(:*) # 999! 
    end 
    end 
end 

Test.run_threads(:do_io) 
#=> ~ 1 sec 
Test.run_forks(:do_io) 
#=> ~ 1 sec 
Test.run_normal(:do_io) 
#=> ~ 4 sec 

Test.run_threads(:do_non_io) 
#=> ~ 7.6 sec 
Test.run_forks(:do_non_io) 
#=> ~ 3.5 sec 
Test.run_normal(:do_non_io) 
#=> ~ 7.2 sec 

việc IO là 4 lần nhanh hơn trong Chủ đề và Processes trong khi việc làm phi IO trong Processes một nhanh gấp hai lần sau đó Chủ đề và phương pháp đồng bộ.

Cũng trong Ruby trình bày Fibers nhẹ "corutines" và tuyệt vời em-synchrony gem để xử lý các quá trình không đồng bộ

+0

Tôi đã cập nhật bài đăng của mình với hai ví dụ khác đang thực hiện một số IO. – HappyDeveloper

+0

IO này vẫn còn quá nhanh để ảnh hưởng đến hiệu suất – fl00r

+0

Kiểm tra cập nhật của tôi với Quy trình – fl00r

4

fl00r là đúng, khóa thông dịch toàn cầu ngăn chặn nhiều luồng chạy cùng lúc trong ruby, trừ IO.

Thư viện parallel là một thư viện rất đơn giản hữu ích cho các hoạt động thực sự song song. Cài đặt với gem install parallel. Dưới đây là ví dụ của bạn viết lại để sử dụng nó:

require 'parallel' 
class Test 
    ITERATIONS = 1000 

    def run_parallel() 
    start = Time.now 

    results = Parallel.map([1,2,3,4]) do |val| 
     do_iterations 
    end 

    # do what you want with the results ... 
    puts Time.now - start 
    end 

    def run_normal 
    start = Time.now 

    do_iterations 
    do_iterations 
    do_iterations 
    do_iterations 

    puts Time.now - start 
    end 

    def do_iterations 
    1.upto ITERATIONS do |i| 
     999.downto(1).inject(:*) # 999! 
    end 
    end 
end 

Trên máy tính của tôi (4 cpu), Test.new.run_normal mất 4,6 giây, trong khi Test.new.run_parallel mất 1,65 giây.

+0

Wow Tôi không biết về đá quý đó. Tôi sẽ cung cấp cho nó một thử – HappyDeveloper

+3

@ HappyDeveloper Chỉ cần cẩn thận, nó sẽ đẻ trứng các quy trình theo mặc định với ống như cơ chế trao đổi. Nó không phải là một sợi và nó không phải là trọng lượng nhẹ. Và tôi nghi ngờ bạn có bất kỳ lợi thế nào nếu bạn sử dụng tùy chọn ': in_threads' với Ruby bình thường. –

3

Hành vi của chuỗi được xác định bằng cách triển khai. JRuby, ví dụ, thực hiện các chủ đề với các chủ đề JVM, mà lần lượt sử dụng các chủ đề thực.

Chỉ có Global Interpreter Lock vì lý do lịch sử. Nếu Ruby 1.9 chỉ đơn giản là giới thiệu các chủ đề thực sự từ hư không, khả năng tương thích ngược sẽ bị phá vỡ, và nó sẽ làm chậm sự chấp nhận của nó hơn nữa.

This answer bởi Jörg W Mittag cung cấp sự so sánh tuyệt vời giữa các mô hình luồng của các triển khai Ruby khác nhau. Chọn một trong đó là thích hợp cho nhu cầu của bạn.

Với những gì đã nói, chủ đề có thể được sử dụng để chờ đợi cho một quá trình con để kết thúc:

pid = Process.spawn 'program' 
thread = Process.detach pid 

# Later... 
status = thread.value.exitstatus 
2

Thậm chí nếu Chủ đề không thực hiện song song họ có thể là một cách đơn giản, rất hiệu quả của việc hoàn thành một số nhiệm vụ , chẳng hạn như các công việc kiểu cron trong quá trình. Ví dụ:

Thread.new{ loop{ download_nightly_logfile_data; sleep TWENTY_FOUR_HOURS } } 
Thread.new{ loop{ send_email_from_queue; sleep ONE_MINUTE } } 
# web server app that queues mail on actions and shows current log file data 

Tôi cũng sử dụng Chủ đề trong máy chủ DRb để xử lý các phép tính dài hạn cho một trong các ứng dụng web của tôi. Máy chủ web bắt đầu tính toán trong một chuỗi và ngay lập tức tiếp tục phản hồi các yêu cầu web. Nó có thể theo dõi định kỳ về tình trạng của công việc và xem nó đang tiến triển như thế nào. Để biết thêm chi tiết, hãy đọc DRb Server for Long-Running Web Processes.

1

Đối với một cách đơn giản để thấy sự khác biệt, sử dụng giấc ngủ thay cho IO mà còn phụ thuộc vào quá nhiều yếu tố:

class Test 


ITERATIONS = 1000 

    def run_threads 
    start = Time.now 
    threads = [] 

    20.times do 
     threads << Thread.new do 
     do_iterations 
     end 
    end 

    threads.each {|t| t.join } # also can be written: threads.each &:join 

    puts Time.now - start 
    end 

    def run_normal 
    start = Time.now 

    20.times do 
     do_iterations 
    end 

    puts Time.now - start 
    end 

    def do_iterations 
    sleep(10) 
    end 
end 

này sẽ có một sự khác biệt giữa các giải pháp ren ngay cả trên MRB, với GIL

Các vấn đề liên quan