Пример условной переменной простого потока, дающий тупик в ruby - PullRequest
0 голосов
/ 29 августа 2018

Привет, я играю с потоками и условными переменными в ruby, и я получаю очень запутанные результаты, которые не имеют смысла. Я следую примеру ConditionVariable из документации по ruby, и все, кажется, идет по плану:

mutex = Mutex.new
resource = ConditionVariable.new

waiting_thread = Thread.new {
  mutex.synchronize {
    puts "Thread 'a' now needs the resource"
    resource.wait(mutex)
    puts "'a' can now have the resource"
    "a can now have the resource"
  }
}

signal_thread = Thread.new {
  mutex.synchronize {
    puts "Thread 'b' has finished using the resource"
    resource.signal
  }
}

При запуске этого кода я получаю очень ожидаемый вывод:

=> Thread 'a' now needs the resource
=> Thread 'b' has finished using the resource
=> 'a' can now have the resource

Однако, МОМЕНТ, который я немного изменил на join или получил value от waiting_thread, он взорвался с Deadlock фатальной ошибкой.

waiting_thread.value
signal_thread

Выходы:

= Ошибка / Ошибка: wait_thread.value - Живых тем не осталось. Тупик

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

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

Это несколько важно не только для моего понимания, но и для одновременного тестирования. Как я могу использовать join и value с ConditionVariables, чтобы произвести то, что я ищу?

1 Ответ

0 голосов
/ 29 августа 2018

Я думаю, что код из документации по Ruby может немного вводить в заблуждение, поскольку он не говорит вам, что отправка сигнала нигде не буферизуется, если получатель его не ждет.

Таким образом, ситуация, которая приведет к тупику, произойдет следующим образом:

  1. signal_thread входит в критическую секцию и звонит resource.signal. Этот сигнал будет потерян.

  2. signal_thread готово и выходит.

  3. waiting_thread входит в критическую секцию и вызывает resource.wait. Теперь он заблокирован в ожидании сигнала, который никогда не приходит.

  4. Все темы заблокированы или неактивны. Больше нет активных потоков, поэтому никто не может разбудить waiting_thread --> тупиковая ошибка.

Вы можете получить случайную ошибку взаимоблокировки на примере кода, в зависимости от вашего процессора, ОС и положения солнца или луны, если вы просто продолжите его запускать, поскольку порядок выполнения signal_thread и waiting_thread не является детерминированным. Порядок является случайным, поэтому тупиковая ситуация может возникнуть или не произойти, но она может произойти в зависимости от порядка выполнения.

Теперь, как вы это решаете? Ну, вам нужно гарантировать waiting_thread ожидания, прежде чем signal_thread сигналов. Мы можем сделать это используя Queue, вот так:

mutex = Mutex.new
resource = ConditionVariable.new

sync_queue = Queue.new

waiting_thread = Thread.new {
  mutex.synchronize {
    puts "Waiting thread sending sync message..."
    sync_queue << 1

    puts "Thread 'a' now needs the resource"
    resource.wait(mutex)
    puts "'a' can now have the resource"
    "a can now have the resource"
  }
}

signal_thread = Thread.new {
  puts "Signal thread waiting for sync..."
  # signal_thread will sleep here, until there is something in the queue to pop.
  # This guarantees the right execution order. 
  sync_queue.pop

  mutex.synchronize {
    puts "Thread 'b' has finished using the resource"
    resource.signal
  }
}

waiting_thread.value

Теперь код является детерминированным, и waiting_thread всегда будет ждать, прежде чем signal_thread подаст сигнал, и код будет работать как положено.

Вам просто нужно знать, что вызов signal переменной условия идет вверх, если никто не ждет на другом конце. Я думаю, что эта важная информация отсутствует в документации.

В дополнение к этому пример ресурса не очень хороший пример проверки доступности ресурса в критическом разделе из-за этой проблемы. Если signal_thread уже использовал ресурс, то waiting_thread никогда не узнает его.

В реальной ситуации необходимо, чтобы потоки распределяли дополнительные данные, чтобы один поток мог проверить, используется ли ресурс, и только тогда ждут сигнала. Если ресурс еще не используется, ожидание сигнала не требуется и фактически не должно выполняться вообще.

т.е. ConditionVariable не должен использоваться для проверки состояния ресурса, только для сигнализации. В этом случае мы используем условные переменные более подходящим образом.

...