当我第一次发现线程时,我尝试通过在许多线程中调用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_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 :(得分:7)
只有当你有一些缓慢的IO时,线程才会更快。
在Ruby中你有全局解释器锁,所以一次只能有一个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
线程和进程中的IO作业快4倍,而进程中的非IO作业比线程和同步方法快两倍。
同样在Ruby中提供Fibers轻量级“corutines”和令人敬畏的em-synchrony gem来处理异步进程
答案 1 :(得分:4)
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
在我的电脑上(4 cpus),Test.new.run_normal
需要4.6秒,而Test.new.run_parallel
需要1.65秒。
答案 2 :(得分:3)
线程的行为由实现定义。例如,JRuby使用JVM线程实现线程,而JVM线程又使用真实线程。
Global Interpreter Lock只是出于历史原因。如果Ruby 1.9只是简单地引入了真正的线程,那么向后兼容性就会被打破,而且它的采用速度会更慢。
This answer的{p> Jörg W Mittag提供了各种Ruby实现的线程模型之间的出色比较。选择一个适合您需求的产品。话虽如此,可以使用线程等待子进程完成:
pid = Process.spawn 'program'
thread = Process.detach pid
# Later...
status = thread.value.exitstatus
答案 3 :(得分:2)
即使Threads不并行执行,它们也是一种非常有效,简单的方法来完成某些任务,例如进程内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服务器中使用Threads来处理我的某个Web应用程序的长时间运行计算。 Web服务器在线程中启动计算并立即继续响应Web请求。它可以定期查看工作状态,并了解工作进展情况。有关详细信息,请阅读DRb Server for Long-Running Web Processes。
答案 4 :(得分:1)
要想看到区别的简单方法,请使用Sleep而不是依赖于太多变量的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
之间也存在差异