简单线程条件变量示例在Ruby中产生死锁

时间:2018-08-29 02:09:44

标签: ruby multithreading thread-safety signals deadlock

嗨,我正在使用ruby中的线程和条件变量,并且得到了一些非常混乱的结果,这是没有意义的。我正在按照ruby文档中的ConditionVariable example进行操作,一切似乎都按计划进行:

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

输出:

  

=失败/错误:waiting_thread.value-          没有活动线程了。僵局?

我可以模糊地了解正在发生的事情-当waiting_thread无限期锁定时,两者都试图在同一个互斥锁上进行同步。

但是在那种情况下,为什么初始代码可以完美地工作以预期的异步结果给出put语句?

这不仅对我的理解很重要,而且对于同时进行测试也很重要。如何将joinvalueConditionVariables结合使用以产生所需的内容?

1 个答案:

答案 0 :(得分:1)

我认为Ruby文档中的代码可能会产生误导,因为它不会告诉您,如果接收方不等待,发送信号不会在任何地方缓冲。

因此,将导致死锁的情况将发生如下情况:

  1. signal_thread进入关键部分并调用resource.signal。该信号将丢失。

  2. signal_thread完成,然后退出。

  3. waiting_thread进入关键部分并调用resource.wait。现在它被锁定,等待一个永远不会到来的信号。

  4. 所有线程已锁定或处于非活动状态。没有更多活动线程,因此没有人能够唤醒waiting_thread -->死锁错误。

如果继续运行,则示例代码可能会随机出现死锁错误,具体取决于您的CPU,操作系统以及太阳或月亮的位置,因为执行signal_thread的顺序waiting_thread是不确定的。该顺序是随机的,因此可能会或可能不会发生死锁,但是死锁可能会根据执行顺序而发生。

现在您如何解决呢?好吧,您需要保证waiting_threadsignal_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将永远不会知道它。

在实际情况下,线程之间需要共享其他数据,以便一个线程可以检查资源是否正在使用,只有THEN等待信号。如果尚未使用该资源,则不需要等待信号,并且实际上根本不应该这样做。

即ConditionVariable不应用于检查资源状态,而仅用于信令。在这种情况下,我们将更适当地使用条件变量。