Ruby DRb - потокобезопасность

Я работаю над многопроцессорным сценарием обработки файлов. После проверки потоков / разветвления я узнал о IPC (труба / сокет) и, что не менее важно, о DRb. Похоже, наиболее способный из всех вариантов и относительно удобный для пользователя.

Я читал о безопасности потоков по адресу: https://en.wikibooks.org/wiki/Ruby_Programming/Standard_Library/DRb

Но когда я попробовал их примеры, я не смог получить многопоточный результат.

Потокобезопасный сервер:

require 'drb'
require 'thread'

class MyStore
  def initialize
    @hash = { :counter=>0 }
    @mutex = Mutex.new
  end
  def inc(elem)
    @mutex.synchronize do
      self[elem] = self[elem].succ
    end
  end
  def [](elem)
    @hash[elem]
  end
  def []=(elem,value)
    @hash[elem] = value
  end
end

mystore = MyStore.new
DRb.start_service('druby://localhost:9000', mystore)
DRb.thread.join

Клиент:

require 'drb'    
obj = DRbObject.new(nil, 'druby://localhost:9000')
STDOUT.sync = true 

100.times do
    puts obj[:counter]
    obj.inc(:counter)
    obj[:lastaccess] = Time.now
end

Я запускаю серверный код первым в фоновом режиме. Позже я запускаю клиентский код дважды:

ruby client.rb > 1.txt & ; ruby client.rb > 2.txt

Теперь я ожидаю увидеть разные числа в файлах 1.txt и 2.txt, поскольку каждый клиент получает контроль над счетчиком и не освобождает его до тех пор, пока он не выполнит приращение.

Какую очевидную проблему я пропускаю?:)

2 ответа

Решение

Проблема внутри вашей петли. Сервера inc метод является потокобезопасным. Тем не менее, ваш доступ к obj[:counter] не является потокобезопасным. Таким образом, вы заметите, что когда вы запускаете свой пример, он делает 200 полных приращений (100 для каждого процесса), потому что вы видите, что последнее напечатанное число равно 199. Это означает, что inc запросы правильно ставятся в очередь и выполняются индивидуально.

Причина, по которой вы на самом деле не видите все 200 чисел (0-199), напечатанных индивидуально (то есть вы видите несколько дублирующих чисел в двух файлах), заключается в том, что ваш цикл просто выполняется puts obj[:counter] как только он попадет в эту строку кода. Текущее значение obj[:counter] печатается независимо от состояния мьютекса, потому что вы не проверяете, заблокирован ли он в данный момент. Это означает, что каждый файл печатает 100 полных чисел в диапазоне от 0 до 1, но они не обязательно различаются между двумя файлами.

Чтобы ваш пример работал, вы должны выполнить печать внутри кода, заблокированного мьютексом, а затем добавить дополнительный аргумент в функцию, чтобы вы могли проверить, из какого клиентского процесса он пришел. Или, на мой взгляд, вы уже доказали, что это сработало, потому что увеличение происходит в 200 раз.

Майк отлично ответил, но я не могу добавить красивый комментарий в комментарии, поэтому отвечаю отдельно.

Я изменяю раздел мьютекса на:

    @mutex.synchronize do
      self[elem] = self[elem].succ
      return @hash[elem]
    end

И клиент должен:

    num = obj.inc(:counter)
    puts num

Результатом были слишком взаимоисключающие файлы.

1.txt:

1
3
4
6
8
10

2.txt:

2
5
7
9
11
13

Последнее число равно 200, как и ожидалось двумя процессами, каждый из которых увеличивается в 100 раз.

Другие вопросы по тегам