如果它们不是真正并行的话,我可以为Ruby线程提供什么用途?

Hap*_*per 6 ruby parallel-processing multithreading

当我第一次发现线程时,我尝试通过在许多线程中调用sleep来检查它们是否按预期工作,而不是正常调用sleep.它奏效了,我很开心.

但后来我的一个朋友告诉我,这些线程并不是真正平行的,睡眠必须是伪造的.

所以现在我写了这个测试来做一些真正的处理:

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 Code Online (Sandbox Code Playgroud)

现在我很难过,因为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
Run Code Online (Sandbox Code Playgroud)

性能结果仍然相同:正常>线程.

但现在我不确定我的VM是否能够使用所有核心.我测试的时候会回来的.

fl0*_*00r 7

只有当你有一些缓慢的IO时,线程才会更快.

在Ruby中,你有Global Interpreter Lock,所以一次只能有一个Thread工作.因此,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
Run Code Online (Sandbox Code Playgroud)

线程和进程中的IO作业快4倍,而进程中的非IO作业比线程和同步方法快两倍.

同样在Ruby中呈现Fibers轻量级"corutines"和令人敬畏的em-synchrony gem来处理异步进程


Dav*_*ani 5

fl00r 是对的,全局解释器锁可以防止 ruby​​ 中多个线程同时运行,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
Run Code Online (Sandbox Code Playgroud)

在我的电脑(4 CPU)上,Test.new.run_normal需要 4.6 秒,而Test.new.run_parallel需要 1.65 秒。

  • @HappyDeveloper 请小心,默认情况下它会以管道作为交换机制生成进程。它不是一个线程,也不是轻量级的。如果您在普通 Ruby 中使用 `:in_threads` 选项,我怀疑您是否有任何优势。 (3认同)