Ruby多线程性能问题

时间:2013-06-18 14:37:39

标签: ruby multithreading concurrency

我正在构建Ruby应用程序。我有一组想要灰度的图像。我的代码曾经是这样的:

def Tools.grayscale_all_frames(frames_dir,output_dir)
    number_of_frames = get_frames_count(frames_dir)
    img_processor = ImageProcessor.new(frames_dir)
    create_dir(output_dir)

    for i in 1..number_of_frames
        img_processor.load_image(frames_dir+"/frame_%04d.png"%+i)
        img_processor.greyscale_image
        img_processor.save_image_in_dir(output_dir,"frame_%04d"%+i)
    end
end
线程化代码后

def Tools.greyscale_all_frames_threaded(frames_dir,output_dir)
    number_of_frames = get_frames_count(frames_dir)
    img_processor = ImageProcessor.new(frames_dir)
    create_dir(output_dir)
    greyscale_frames_threads = []

    for frame_index in 1..3
        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 "Starting Threads"
    greyscale_frames_threads.each { |thread| thread.join }

end

我期望的是为每个图像生成一个线程。我有1000张图片。分辨率为1920 * 1080。所以我怎么看事情是这样的。我有一个线程数组,我称之为.join。所以join会接受所有线程并一个接一个地启动它们吗?这是否意味着它会等到线程1完成然后启动线程2?那么多线程有什么意义呢?

我想要的是:

同时运行所有线程而不是一个接一个地运行。所以在数学上,它将在完成1帧的同时完成所有1000帧,对吗?

还有人可以解释一下.join的作用吗? 从我的理解.join将停止主线程,直到你的线程完成或完成? 如果你不使用.join,那么线程将运行后台,主线程将继续。

那么使用.join有什么意义呢?我希望我的主线程继续运行并让后台的其他线程做什么?

感谢您的帮助/澄清!!

2 个答案:

答案 0 :(得分:3)

只有拥有1000个CPU核心和大量(读取:数百和数百个)RAM才能实现。

连接点不是启动线程,而是等待线程完成。因此,在线程数组上调用join是等待所有线程完成的常见模式。

解释所有这些,并澄清你的误解,这需要深入挖掘。在C / Assembler级别,现代操作系统(Win,Mac,Linux和其他一些操作系统)使用抢先式调度程序。如果你只有一个核心,那么两个以paralel运行的程序就是一个完整的错觉。实际上,内核每隔几毫秒就会在两者之间进行切换,这使得所有人都可以慢慢地处理人类并行处理的错觉。

在更新,更现代的CPU中,通常有多个核心。今天最强大的CPU可以达到(我认为)16个真核+ 16个超线程核(参见here)。这意味着您实际上可以完全并行运行32个任务 。但即使这样也无法确保如果你启动32个线程,它们将同时完成。

由于对核心(某些缓存,所有RAM,硬盘,网卡等)之间共享的资源的竞争以及抢占式调度的基本随机性质,因此可以估计线程所花费的时间量。一定的范围,但不完全是。

不幸的是,当你到达Ruby时,所有这些都会崩溃。由于有关线程模型兼容性的一些内部细节,一次只能有一个线程执行 ruby​​ 代码。所以,如果您的图像处理是在C中完成的,那就是快乐的快乐。如果它是用Ruby编写的,那么现在世界上所有的步骤都不会帮助你。

为了能够实际并行运行 Ruby 代码,您必须使用forkfork仅适用于Linux和Mac,而不适用于Windows,但您可以将其视为道路上的分叉。一个过程进入,两个过程出来。多个进程可以同时在所有不同的核心上运行。

所以,请听@ Stefan的建议:使用一个队列和一些工作线程=来自CPU核心数。并且不要指望你的电脑如此之多。现在你知道为什么了;)。

答案 1 :(得分:0)

  

所以join将接受所有线程并一个接一个地启动它们?

不,在调用Thread#new时会启动线程。它创建一个新线程并在该线程中执行给定的块。

  

join将停止主线程,直到您的线程完成或已完成?

是的,它会暂停执行,直到接收者(你的每个线程)都存在。

  

那么使用join是什么意思?

有时你想并行开始一些任务,但你必须等待每个任务完成才能继续。

  

我希望我的主线程继续运行并让后台其他线程做东西

然后不要致电join

毕竟,并行启动1,000个线程并不是一个好主意。您的计算机只能在CPU可用时并行运行任意数量的任务。因此,不是启动1,000个线程,而是将您的作业/任务放在队列/池中,并使用一些工作线程处理它们(CPU数量=工作者数量)。