0

我正在构建 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 有什么意义呢?我希望我的主线程继续运行并让其他线程在后台执行操作?

感谢您的帮助/澄清!

4

2 回答 2

3

仅当您拥有 1000 个 CPU 内核和大量(读取:数百和数百个)RAM 时,这才是正确的。

join的目的不是启动线程,而是等到线程完成。因此,在线程数组上调用 join 是等待它们全部完成的常见模式。

解释所有这些,并澄清你的误解,这需要更深入地挖掘。在 C/Assembler 级别,大多数现代操作系统(Win、Mac、Linux 和其他一些)使用抢占式调度程序。如果你只有一个核心,两个程序并行运行完全是一种错觉。实际上,内核每隔几毫秒就会在两者之间切换,给所有使用缓慢处理的人带来并行处理的错觉。

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

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

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

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

因此,请接受@Stefan 的建议:使用队列和多个工作线程 = 到 CPU 核心数。并且不要对您的计算机有太多期望。现在你知道为什么了;)。

于 2013-06-18T15:21:01.167 回答
0

那么join将获取所有线程并一个接一个地启动它们吗?

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

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

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

那么使用有什么意义join呢?

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

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

那就别打电话了join

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

于 2013-06-18T15:16:33.933 回答