2

我们如何锁定已被多个 ruby​​ 进程共享的 IO?

考虑这个脚本:

#!/usr/bin/ruby -w
# vim: ts=2 sw=2 et
if ARGV.length != 2
  $stderr.puts "Usage: test-io-fork.rb num_child num_iteration"
  exit 1
end
CHILD = ARGV[0].to_i
ITERATION = ARGV[1].to_i

def now
  t = Time.now
  "#{t.strftime('%H:%M:%S')}.#{t.usec}"
end

MAP = %w(nol satu dua tiga empat lima enam tujuh delapan sembilan)

IO.popen('-', 'w') {|pipe|
  unless pipe
    # Logger child
    File.open('test-io-fork.log', 'w') {|log|
      log.puts "#{now} Program start"
      $stdin.each {|line|
        log.puts "#{now} #{line}"
      }
      log.puts "#{now} Program end"
    }
    exit!
  end
  pipe.sync = true
  pipe.puts "Before fork"
  CHILD.times {|c|
    fork {
      pid = Process.pid
      srand
      ITERATION.times {|i|
        n = rand(9)
        sleep(n / 100000.0)
        pipe.puts "##{c}:#{i} #{MAP[n]} => #{n}, #{n} => #{MAP[n]} ##{c}:#{i}"
      }
    }
  }

}

并尝试这样:

./test-io-fork.rb 200 50

正如预期的那样,test-io-fork.log 文件将包含 IO 竞争条件的迹象。

我想要实现的是为自定义 GPS 协议制作一个 TCP 服务器,它将 GPS 点保存到数据库中。因为该服务器将处理 1000 个并发客户端,所以我想将数据库连接限制为只有一个孩子,而不是同时打开 1000 个数据库连接。该服务器将在 linux 上运行。

4

1 回答 1

2

更新

接受答案后更新可能是不好的形式,但原件有点误导。ruby 是否单独write(2)调用自动附加的换行符取决于输出 IO 对象的缓冲状态。

$stdout(当连接到 tty 时)通常是行缓冲的,因此puts()- 给定大小合理的字符串 - 隐式添加换行符的效果是对write(2). 然而,正如 OP 所发现的那样,与IO.pipe和并非如此。$stderr

原始答案

将您的主要pipe.puts()参数更改为换行符终止的字符串:

pipe.puts "##{c} ... #{i}\n"  # <-- note the newline

为什么?您pipe.sync希望管道写入是原子的且非交错的,因为它们(可能)小于PIPE_BUF字节。但它不起作用,因为 ruby​​ 的管道puts()实现单独调用 write(2) 以附加尾随换行符,这就是为什么有时您的写入会在您期望换行符的地方交错。

这是您的脚本的 fork-following strace 的确证摘录:

$ strace -s 2048 -fe trace=write ./so-1326067.rb
....
4574  write(4, "#0:12 tiga => 3, 3 => tiga #0:12", 32) = 32
4574  write(4, "\n", 1)
....

但是放入您自己的换行符可以解决问题,确保您的整个记录​​在一个系统调用中传输:

....
5190  write(4, "#194:41 tujuh => 7, 7 => tujuh #194:41\n", 39 <unfinished ...>
5179  write(4, "#183:38 enam => 6, 6 => enam #183:38\n", 37 <unfinished ...>
....

如果由于某种原因对您不起作用,您将不得不协调一个进程间互斥锁(如File.flock())。

于 2009-08-25T04:57:43.907 回答