Я думаю, что код из документации по Ruby может немного вводить в заблуждение, поскольку он не говорит вам, что отправка сигнала нигде не буферизуется, если получатель его не ждет.
Таким образом, ситуация, которая приведет к тупику, произойдет следующим образом:
signal_thread
входит в критическую секцию и звонит resource.signal
. Этот сигнал будет потерян.
signal_thread
готово и выходит.
waiting_thread
входит в критическую секцию и вызывает resource.wait
. Теперь он заблокирован в ожидании сигнала, который никогда не приходит.
Все темы заблокированы или неактивны. Больше нет активных потоков, поэтому никто не может разбудить 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 не должен использоваться для проверки состояния ресурса, только для сигнализации. В этом случае мы используем условные переменные более подходящим образом.