为什么使用迭代器调用 Thread 对象的 #join 与使用循环调用时的工作方式不同?

Why does #join on a Thread object work differently when called with an iterator than with a loop?

在循环内对 Thread 对象应用#join 会按顺序执行它们。

5.times do |x|
  Thread.new {
    t= rand(1..5) * 0.25
    sleep(t)
    puts "Thread #{x}:  #{t} seconds"
   }.join
end

# Output
# Thread 0:  1.25 seconds
# Thread 1:  1.25 seconds
# Thread 2:  0.5 seconds
# Thread 3:  0.75 seconds
# Thread 4:  0.25 seconds

另一方面,将#join 应用于带有迭代器的 Thread 对象数组会同时执行它们。为什么?

threads = []

5.times do |x|
  threads << Thread.new {
    t = rand(1..5) * 0.25
    sleep(t)
    puts "Thread #{x}:  #{t} seconds"
  }
end

threads.each(&:join)

# Output
# Thread 1:  0.25 seconds
# Thread 3:  0.5 seconds
# Thread 0:  1.0 seconds
# Thread 4:  1.0 seconds
# Thread 2:  1.25 seconds

这里有几点需要说明。

线程启动时

使用#new、#start、#fork 实例化线程会立即启动该线程的代码。这 运行s 与主线程并发。但是,在没有 'joining' 的短脚本中调用线程时,主线程通常会在被调用线程有机会完成之前结束。对于业余程序员来说,它给人的错误印象是#join 启动 线程。

thread = Thread.new {
   puts "Here's a thread"
}

# (No output)

向调用主线程添加一个短暂的延迟,让被调用线程有机会完成。

thread = Thread.new {
   puts "Here's a thread"
}

sleep(2)

# Here's a thread

#join 的实际作用

#join 阻塞 main 线程,并且只阻塞调用线程,直到被调用线程完成。任何先前调用的线程都不受影响;他们已经 运行 同时并继续这样做。

原例子解释

在第一个示例中,循环启动了一个线程,并立即 'joins' 它。因为#join 阻塞了主线程,所以循环会暂停,直到第一个线程完成。然后循环迭代,启动第二个线程,'joins' 它,并再次暂停循环,直到该线程完成。纯粹是顺序的,完全否定线程的意义。

5.times do |x|
  Thread.new {
    t= rand(1..5) * 0.25
    sleep(t)
    puts "Thread #{x}:  #{t} seconds"
   }.join                             # <--- this #join is the culprit.
end

用户 Solomon Slow 在他原来的评论中说得最好 [=7​​3=]。

It never makes sense to "join" a thread immediately after creating it. The only reason for ever creating a thread is if the caller is going to do something else while the new thread is running. In your second example, the "something else" that the caller does is, it creates more threads.

第二个例子正确地处理了多线程。循环启动一个线程,迭代,启动下一个线程,迭代,等等。因为我们没有在循环中使用#join ,所以主线程不断迭代并启动所有线程。

那么在迭代器中使用#join 为什么不会造成与第一个示例相同的问题呢?因为这些线程已经运行并发了。请记住,#join 只会阻塞主线程,直到 'joined' 线程完成。这个被调用线程和所有其他被调用线程自创建它们的循环以来一直 运行ning,它们将继续 运行 并独立于主线程和彼此完成。 'Joining' 所有线程依次告诉主线程:

  • 在线程 1 完成之前不要继续(但有可能此线程以及其他线程中的一些、全部或 none 可能已经完成)。
  • 在线程 2 完成之前不要继续(但有可能这个线程以及一些、全部或 none 剩余线程可能已经完成)。
  • ...
  • 在线程 5 完成之前不要继续(但有可能这个线程已经完成,而所有剩余的线程肯定已经完成)。

实际上这最后一行顺序指示 main 线程暂停,但它不会妨碍被调用的线程。

threads.each(&:join)

我还发现 this explanation 很有帮助。