4

在我看来,在 Ruby MRI 1.8.7 中写入文件是完全线程安全的。

示例 1 - 完美的结果:

File.open("test.txt", "a") { |f|
  threads = []
  1_000_000.times do |n|
    threads << Thread.new do
      f << "#{n}content\n"
    end
  end
  threads.each { |t| t.join }
}

示例 2 - 完美的结果(但速度较慢):

threads = []
100_000.times do |n|
  threads << Thread.new do
    File.open("test2.txt", "a") { |f|
      f << "#{n}content\n"
    }
  end
end
threads.each { |t| t.join }

所以,我无法重建我面临并发问题的场景,可以吗?

如果有人能向我解释为什么我仍然应该在这里使用 Mutex,我将不胜感激。

编辑:这是另一个更复杂的例子,它工作得很好并且没有显示并发问题:

def complicated(n)
  n.to_s(36).to_a.pack("m").strip * 100
end

items = (1..100_000).to_a

threads = []
10_000.times do |thread|
  threads << Thread.new do
    while item = items.pop

      sleep(rand(100) / 1000.0)
      File.open("test3.txt", "a") { |f|
        f << "#{item} --- #{complicated(item)}\n"
      }

    end
  end
end
threads.each { |t| t.join }
4

1 回答 1

3

我也无法产生错误。

您可能在这里遇到了文件锁。如果您希望多个线程写入同一个文件,它们应该都使用相同的文件对象,如下所示:

File.open("test.txt", "a") do |fp|
  threads = []
  
  500.times do |time|
    threads << Thread.new do
      fp.puts("#{time}: 1")
      sleep(rand(100) / 100.0)
      fp.puts("#{time}: 2")
    end
  end
  
  threads.each(&:join)
end

在这个例子中,GIL 可能会让你避免任何真正的线程错误,但我不确定在使用真正线程的 JRuby 下会发生什么,并且两个写入可能同时发生。其他具有真正线程的 Ruby 引擎也是如此。

关于你应该在哪里用锁保护你的代码的问题归结为,如果你想依赖你正在使用的 Ruby 引擎应该拯救你,或者你想编写一个“应该”在所有 Ruby 引擎上工作的解决方案,无论它们是否具有使您免于并发问题的内置功能。

另一个问题是,您的操作系统和/或文件系统是否正在通过文件锁将您从线程错误中拯救出来,并且您的代码是否应该独立于操作系统和/或文件系统,这意味着您不会依赖文件-系统锁定以确保您的文件打开和写入被操作系统和/或文件系统正确同步。

我会冒昧地说,这似乎是一种很好的做法,你也可以在你身边实现锁,以确保你的代码继续工作,不管别人是哪个 Ruby 引擎、操作系统或文件系统即使大多数现代 Ruby 引擎、操作系统和文件系统都内置了这些功能,也会继续使用您的代码。

于 2013-02-27T12:23:54.183 回答