2012-04-19 32 views
6

當我第一次發現線程時,我試着通過在很多線程中調用睡眠來檢查它們是否按預期工作,而不是正常調用睡眠。它工作,我很高興。如果Ruby線程不是真的平行,我可以給Ruby線程什麼?

但後來我的一位朋友告訴我,這些線程並不是真的平行,而且睡眠一定是在僞裝它。

所以,現在我寫這個測試做一些真正的處理:

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 

現在我很傷心,因爲run_threads()不僅沒有表現得比較好run_normal,這是更慢!

那麼爲什麼我應該使用線程複雜化我的應用程序,如果它們不是真的平行的話?

**更新**

@ fl00r說我可以利用線程,如果我用他們的IO任務,所以我寫了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 

的性能結果仍然相同:正常>線程。

但現在我不確定我的VM是否能夠使用所有內核。當我測試過時會回來。

+0

線很爛,纖維巖! :D – fl00r 2012-04-19 10:21:28

+0

如果你使用官方ruby,線程僞造,但對於jruby和rubinius,我相信它是真正的線程。 – texasbruce 2012-04-19 12:21:47

+0

性能不是使用線程的唯一原因。看到我的答案。 – Phrogz 2012-04-19 15:53:36

回答

7

線程可能會更快,只有當你有一些緩慢的IO。

在Ruby中,您擁有全局解釋器鎖定功能,因此一次只能有一個線程工作。所以,Ruby花費了很多時間來管理哪些線程應該在某一時刻被觸發(線程調度)。所以在你的情況下,當沒有任何IO時它會變慢!

您可以使用Rubinius或JRuby來使用真正的線程。

實施例與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 

IO作業是在線程和進程快4倍,而在過程非IO作業一個快兩倍那麼線程和同步方法。

在Ruby中

也呈現Fibers輕量級「corutines」,真棒em-synchrony gem處理異步進程

+0

我用另外兩個例子做了一些IO更新我的文章。 – HappyDeveloper 2012-04-19 11:14:04

+0

這個IO仍然太快,不會影響性能 – fl00r 2012-04-19 12:19:26

+0

看看我的更新與進程 – fl00r 2012-04-19 12:25:47

4

fl00r是正確的,全球解釋器鎖防止在紅寶石同時運行多個線程,除了IO。

parallel庫是一個非常簡單的庫,可用於真正的並行操作。用gem install parallel安裝。這裏就是你們的榜樣重寫使用它:

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 

在我的電腦(4個CPU),Test.new.run_normal需要4.6秒,而Test.new.run_parallel需要1.65秒。

+0

哇我不知道那個寶石。我會試一試 – HappyDeveloper 2012-04-19 11:17:30

+3

@HappyDeveloper只要小心點,它會默認使用管道作爲交換機制產生進程。這不是一個線程,它不是輕量級的。如果你在普通的Ruby中使用':in_threads'選項,我懷疑你有什麼優勢。 – 2012-04-19 14:24:02

3

線程的行爲由實現定義。例如,JRuby使用JVM線程實現線程,而JVM線程又使用真正的線程。

Global Interpreter Lock只是因爲歷史原因。如果Ruby 1.9只是簡單地引入了真正的線程,向後兼容性將被打破,並且它會減緩它的採用。

This answer by Jörg W Mittag在各種Ruby實現的線程模型之間提供了極好的比較。選擇一個適合您需求的產品。

隨着中說,線程可以用來等待一個子進程結束:

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

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

即使線程不併行執行,他們可以完成一些任務的一個非常有效的,簡單的方法,例如進程內的cron類型的作業。例如:

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 

我還使用DRb服務器中的線程來處理我的一個Web應用程序的長時間運行計算。 Web服務器在線程中啓動計算並立即繼續響應Web請求。它可以定期查看工作狀態,看看工作進展如何。欲瞭解更多詳情,請閱讀DRb Server for Long-Running Web Processes

1

對於一個簡單的方法,看到了差距,用睡眠代替IO這也依賴於太多的變數:

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 

這將對即使在MRB螺紋解決方案之間的差異,與GIL