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 раз.