Ruby: безопасно ли использовать `srand` в потоке инициализатора?

#ruby #ruby-on-rails-6

#ruby #ruby-on-rails-6

Вопрос:

Безопасен ли поток следующего кода? Я беспокоюсь, что srand может быть переменной, которая не является потокобезопасной. У меня есть приложение rails, и я собираюсь использовать Puma с 5-10 потоками. Я не хочу вызывать проблемы.

 class Test
  def initialize(seed)
    srand seed          # => 1, 222, 222, 111
  end                   # => :initialize

  def letter
    %w[a b c d e f g h i j k l m n o p q r s t u v w x y z aa bb cc dd].sample  # => "g", "u"
  end                                                                           # => :letter
  
  def letter1
    %w[a b c d e f g h i j k l m n o p q r s t u v w x y z aa bb cc dd].sample  # => "g", "u"
  end                                                                           # => :letter1
end                                                                             # => :letter1

Test.new(222).letter   # => "g"
Test.new(222).letter1  # => "g"

Test.new(111).letter   # => "u"
Test.new(111).letter1  # => "u"
  

Ответ №1:

Зависит от того, что вы подразумеваете под «потокобезопасным». Программа все равно будет работать, Ruby не перейдет в несогласованное состояние, и никакие данные не будут потеряны. Однако генератор случайных чисел по умолчанию является глобальным, разделяемым между потоками; это будет зависеть от времени потока, чтобы увидеть, какой поток получает какое случайное число. Если вы хотите, чтобы все потоки просто получали случайные числа из одного RNG, ваш код в порядке, хотя результаты могут быть не повторяемыми, что, вероятно, противоречит цели srand .

Если вы хотите убедиться, что каждый Test из них генерирует числа только независимо (и повторяемо), вы хотите, чтобы у каждого Test был свой собственный генератор случайных чисел:

 class Test
  def initialize(seed)
    @random = Random.new(seed)
  end

  def letter
    %w[a b c d e f g h i j k l m n o p q r s t u v w x y z aa bb cc dd].sample(random: @random)
  end
end

t1 = Test.new(111)
t2 = Test.new(222)
3.times.map { t1.letter }
# => ["u", "m", "u"]
3.times.map { t2.letter }
# => ["u", "m", "u"]