我正在尝试使用Ruby尽可能快地处理1000帧。所以我目前开始1000个线程(每帧一个)。结果很糟糕。它使用大量内存而且速度很慢。我的CPU是http://ark.intel.com/products/67355/ 它说它支持4个线程。 (我猜每个CPU 2个?)。 因此,如果我一次启动4个Ruby线程,等到它们完成,然后再启动4个等等,它将需要250'步骤来完成处理权,而不是1000?
编辑:我的代码现在:
beginning_time = Time.now
limit=1
for frame_index in 1..limit
greyscale_frames_threads << Thread.new(frame_index) { |frame_number|
puts "Loading Image #{frame_number}"
img_processor.load_image(frames_dir+"/frame_%04d.png"%+frame_number)
img_processor.greyscale_image
img_processor.save_image_in_dir(output_dir,"frame_%04d"%+frame_number)
puts "Greyscaled Image #{frame_number}"
}
end
puts "Joining Threads"
greyscale_frames_threads.each { |thread| thread.join } #this blocks the main thread
end_time = Time.now
puts "Time elapsed #{(end_time - beginning_time)*1000} milliseconds"
现在,对于limit = 1,这就是我得到的:
经过的时间23504.805999999997毫秒
现在,对于limit = 2,这就是我得到的:
经过的时间53465.676毫秒
对于limit = 2,我期待23504.805999999997毫秒。
这意味着我的代码失败了。线程在这里没有任何意义。为什么?有人可以向我解释一下吗?
答案 0 :(得分:5)
例如,您可以使用此类创建线程池并安排工作:
class Pool
def initialize(size)
@size = size
@jobs = Queue.new
@pool = Array.new(@size) do |i|
Thread.new do
Thread.current[:id] = i
catch(:exit) do
loop do
job, args = @jobs.pop
job.call(*args)
end
end
end
end
end
def schedule(*args, &block)
@jobs << [block, args]
end
def shutdown
@size.times do
schedule { throw :exit }
end
@pool.map(&:join)
end
end
然后,您可以执行pool = Pool.new(32)
并在此之后执行以下内容:
pool.schedule do
# do your stuff
end
这意味着(在这个例子中)最多会有32个线程,每个线程会弹出你安排的一些工作。
答案 1 :(得分:3)
在标准的ruby解释器中,一次只运行一个线程,因此如果你是计算绑定,则添加线程无济于事。如果您搜索GIL或GVL(全局解释器锁定),您将找到关于此主题的大量内容。如果你是IO绑定的,那么等待IO的线程将放弃控制,因此在这种情况下线程仍然有用。
C扩展也可以放弃GVL,因此可以并行运行多个线程,但这只能在ruby 1.9.3+上进行,并且C扩展需要考虑到这一点。
如果计算并行性对您很重要,您可能需要查看jruby或rubinius,它们都没有GVL。在这两者中,jruby更成熟。
最后,每个任务创建一个线程是浪费的 - 涉及一定数量的开销,因此正常模式是拥有一个可重用的工作线程池。