我正在尝试使用互斥量实现信号量,以了解有关并发基元和模式以及如何编写正确的并发程序的更多信息。
这是我找到的资源: http://webhome.csc.uvic.ca/~mcheng/460/notes/gensem.pdf
第9页上的解决方案#1标记为“不正确”。我实现了算法here。多次运行该程序可能会导致线程冻结。我进行了分析,意识到在d
互斥锁上可能发生以下操作序列:
// d was initialized as locked
d.unlock(); // d is unlocked
d.unlock(); // d is still unlocked (unaffected)
d.lock(); // d is locked
d.lock(); // d is still locked (deadlock)
这将导致最后一个d.lock()
陷入僵局。
解决方案2通过重用互斥体m
,确保了从信号线程到唤醒线程的过渡,从而解决了此问题。我实现了此版本here。
在此解决方案中,d.unlock()
之后,m
保持锁定,这意味着随后
post()
操作将被阻止,直到m
被解锁。那么d.lock()
是
在m.unlock()
之后被调用,请确保d
在处于锁定状态之前
允许随后的post()
操作运行。
尽管我了解此解决方案如何解决该问题,但我很难就其他潜在问题争论其正确性。我们是否可以遵循任何一般性规则和指南来确保,争论甚至证明此类程序的正确性?
由于注释中的下两个解决方案,我想问这个问题。我实现了solution #3和solution #4,但是经过多次测试,它们都具有冻结线程。我不确定这是我的实现问题还是解决方案本身不正确。
如果您能分析这些解决方案的正确性,我将不胜感激,但我比以往任何时候都更想学习一种推理此类算法并验证其正确性的方法。
答案 0 :(得分:1)
关于lockdep的文档(例如:https://lwn.net/Articles/705379/)是描述您正在寻找的一般标准的众多资源之一:您想要一个在依赖关系图中没有循环的实现。
在您的示例中,d
锁定了两次,您有一个依赖图,该依赖图的循环仅由一个节点d
组成,边沿又回到自身。
一种可能对解决方案3和4有所帮助的多线程编码样式将是始终以与获取锁相反的顺序释放锁。在纸上解决问题的总体理解可能会有所帮助,为什么在您的代码上强加此要求至少与说您的实现不需要在其依赖图中没有循环这样严格。
还有一个额外的问题:是否有一些实现的锁和释放顺序不是镜像,但仍然没有循环?
答案 1 :(得分:1)
多次运行代码并希望进行任意的交织以发现与并发相关的错误不是很好的处理方式,但是对于某些初始测试很有用。否则,会有更好的方法。
如果您使用的是C或C ++,则可以查看GCC和Clang中提供的ThreadSanitizer,它将帮助您在运行时检测死锁和其他与并发相关的错误。
更详尽的方法是系统并发测试,该测试将枚举可能的交错并执行它们。您应检出无状态和有状态模型检查等多种技术。
Spin是可用于形式验证的模型检查工具。您可以在Promela中编写模型,并使用LTL进行断言。
最后,这是各种语言的Wikipedia的list of model checkers。