Как заблокировать IO общий с помощью fork в ruby - PullRequest
2 голосов
/ 25 августа 2009

Как мы можем заблокировать ввод-вывод, который был совместно использован несколькими процессами ruby?

Рассмотрим этот скрипт:

#!/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.

Чего я хочу добиться, так это создать TCP-сервер для настраиваемого протокола GPS, который будет сохранять точки GPS в базе данных. Поскольку этот сервер будет обрабатывать 1000 одновременно работающих клиентов, я хотел бы ограничить подключение к базе данных только одним дочерним объектом, вместо этого одновременно открывая 1000 подключений к базе данных. Этот сервер будет работать на Linux.

1 Ответ

2 голосов
/ 25 августа 2009

UPDATE

Обновление может быть плохим после принятия ответа, но оригинал немного вводит в заблуждение. То, будет ли ruby ​​делать отдельный вызов write(2) для автоматически добавляемой новой строки, зависит от состояния буферизации выходного объекта ввода-вывода.

$stdout (при подключении к tty) обычно буферизуется строкой, поэтому эффект puts() - при заданной строке разумного размера - с неявно добавленной новой строкой - это один вызов write(2). Однако в случае с IO.pipe и $stderr, как обнаружил ОП, это не так.

ОРИГИНАЛЬНЫЙ ОТВЕТ

Измените свой главный аргумент pipe.puts() на символ новой строки, оканчивающийся строка:

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

Почему? Вы устанавливаете pipe.sync в надежде, что запись в канал будет атомарной и без чередования, поскольку они (предположительно) меньше PIPE_BUF байтов. Но это не сработало, потому что реализация puts() канала ruby ​​ делает отдельный вызов write (2) для добавления завершающего символа новой строки , и поэтому ваши записи иногда чередуются там, где вы ожидали новую строку.

Вот подтверждающий отрывок из следующей строки вашего сценария:

$ 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()).

...