Рубиновые затворы, темы и область видимости - PullRequest
0 голосов
/ 24 мая 2018

Я пытаюсь написать потоковое приложение на Ruby, и у меня возникли небольшие проблемы с пониманием области видимости переменных, которые передаются в поток через блок.Упрощенная версия кода приведена ниже

def threadRoutine(p1,p2)
  puts "Thread #{Thread.current.object_id} started with parameters #{p1}, #{p2}"
  Kernel.sleep 5
  puts "Thread #{Thread.current.object_id} completed"
end

start = 0
threads = []
(1..10).each do 
  finish = start + 1
  threads << Thread.new{threadRoutine(start,finish)}
  puts "Started thread #{threads[-1].object_id} with parameters #{start},#{finish}"
  start = finish
end

threads.each do |t|
  t.join
end

Вывод этого выглядит следующим образом

Started thread 2002 with parameters 0,1
Thread 2002 started with parameters 0, 1
Started thread 2004 with parameters 1,2
Thread 2004 started with parameters 1, 2
Started thread 2006 with parameters 2,3
Thread 2006 started with parameters 3, 3
Started thread 2008 with parameters 3,4
Thread 2008 started with parameters 4, 4
Started thread 2010 with parameters 4,5
Thread 2010 started with parameters 5, 5

Thread 2010 completed
Thread 2004 completed
Thread 2002 completed
Thread 2006 completed
Thread 2008 completed

Первые два потока успешно начинаются с потока, начинающегося с тех же параметров, которые были переданыиз внешней рутины.После этого (поток 2006 и далее) параметр p1 в потоке получает значение переменной 'start', которая была обновлена ​​после вызова Thread.new

Очевидно, что я неправильно понимаю, как все происходитработать здесь, но я ожидал, что блок, переданный в Thread.new, создаст замыкание со значениями переменных, с которыми был вызван блок.Это, кажется, не происходит последовательно.Обратите внимание, что если я следую за вызовом Thread.new с 1-секундным сном, все работает нормально, но это похоже на хак, и я хотел бы понять, как это сделать правильно.

Любые объяснения и предложения оченьприветствуется.

Вот подробности о версии Ruby, которую я использую

jruby 9.1.4.0 (2.3.1) 2016-09-01 2e1327f Java HotSpot(TM) Client VM 25.51-b03 on 1.8.0_51-b16 +jit [mswin32-x86]

1 Ответ

0 голосов
/ 24 мая 2018

Глядя на этот фрагмент кода:

(1..10).each do 
  finish = start + 1
  threads << Thread.new{threadRoutine(start,finish)}
  puts "Started thread #{threads[-1].object_id} with parameters #{start},#{finish}"
  start = finish
end

Вам нужно понять, что тело Thread.new { } вычисляется не сразу, оно оценивается, когда поток доступен для запуска.

Итак, есть условие гонки, при котором несколько и / или частичных итераций этого цикла происходят до того, как будет вычислено тело Thread.new.В этом случае start и end будут изменены за это время.Причина этого в том, что start определен вне цикла.У вас нет правильного закрытия.

Чтобы решить эту проблему, я могу посоветовать два возможных решения:

  1. Определить переменные внутри блока итерации, а не вне его .Вы уже делаете это с finish, просто нужно сделать это и с start:

    10.times do |start|
      finish = start + 1
      threads << Thread.new{threadRoutine(start,finish)}
      puts "Started thread #{threads[-1].object_id} with parameters #{start},#{finish}"
    end
    

    В Ruby есть закрытие на уровне блоков, если переменная с таким именем уже объявлена ​​ввнешняя сфера.Сравните:

    10.times do
      a ||= 0
      a += 1
      print a
    end
    # => 1111111111
    
    a = 0
    10.times do
      a ||= 0
      a += 1
      print a
    end
    # => 12345678910
    
  2. Переместите вызов Thread.new { } в метод.Таким образом, вы получите метод уровня замыкание по переменным:

    def threadRoutine(p1,p2)
      Thread.new do
        puts "Thread #{Thread.current.object_id} started with parameters #{p1}, #{p2}"
        Kernel.sleep 5
        puts "Thread #{Thread.current.object_id} completed"
      end
    end
    
    # later, in the loop ...
    threads << threadRoutine(start,finish)
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...