2012-04-19 14 views
6

スレッドを最初に発見したときに、通常sleepを呼び出すよりも、多くのスレッドでsleepを呼び出すことで、実際にスレッドが正常に機能していることを確認しました。それは働いて、とても幸せだった。本当に並行していない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 @

** UPDATE **

は、私はIOタスクのためにそれらを使用した場合、私はスレッドを利用することができると言ったので、私はdo_iterationsの2つの以上のバリエーション書いた:

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

+0

公式のルビーを使用するとスレッディングが偽装されますが、jrubyとrubiniusの場合は本当のスレッディングと信じています。 – texasbruce

+0

パフォーマンスはスレッドを使用する唯一の理由ではありません。私の答えを見てください。 – Phrogz

答えて

7

IOが遅い場合にのみスレッドが高速になる可能性があります。

Rubyではグローバルインタープリタロックを使用しているため、一度に1つのスレッドしか動作できません。だから、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ジョブ非IOの仕事しながら、プロセス内のスレッドとプロセスで4倍高速倍の速度その後、スレッドと同期する方法です。

はまた、Rubyで

+0

IOをやっているもっと2つの例を使って投稿を更新しました。 – HappyDeveloper

+0

このIOはまだパフォーマンスに影響を与えるには速すぎます – fl00r

+0

プロセスで更新をチェックしてください – fl00r

4

fl00rが正しいと非同期プロセスを処理するためにFibers軽量「corutines」と素晴らしいem-synchrony gemを提示し、グローバル通訳ロックはIOを除き、ルビーで同時に実行されている複数のスレッドを防ぐことができます。

parallelライブラリは、非常に単純なライブラリであり、真の並列操作に役立ちます。 gem install parallelと一緒にインストールしてください。ここにあなたの例では、それを使用するようにリライトされます。私のコンピュータ(4つのCPU)で

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 

Test.new.run_parallelは1.65秒かかりながら、Test.new.run_normalは、4.6秒かかります。

+0

うわー、私はその宝石について知りませんでした。私はそれを試してみます – HappyDeveloper

+3

@HappyDeveloperちょっと注意してください、それは交換メカニズムとしてパイプでデフォルトでプロセスを生成します。これはスレッドではなく、軽量ではありません。また、通常のRubyで ':in_threads'オプションを使用すると、あなたに利点があるとは思えません。 –

3

スレッドの動作は、実装によって定義されます。たとえば、JRubyはJVMスレッドをスレッドに実装し、実際には実際のスレッドを使用します。

Global Interpreter Lockは、歴史的な理由からのみ存在します。 Ruby 1.9が単に実際のスレッドを導入していなかった場合、下位互換性が損なわれ、採用がさらに遅くなってしまいます。

This answerJö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 

また、私のWebアプリケーションの1つで長時間の計算を処理するためにDRbサーバーのスレッドを使用します。 Webサーバーはスレッド内で計算を開始し、すぐにWeb要求に応答し続けます。それは定期的に仕事の状況を覗き見ることができ、それがどのように進行しているかを見ることができます。詳細はDRb Server for Long-Running Web Processesをご覧ください。違いを確認するための簡単な方法ではなく、あまりにも多くの変数に依存していIOの使用のために睡眠

1

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 

これはGILで、でもMRB上のネジソリューションの違いを持っ​​ています

関連する問題