这里我们只是关注了一些多线程之间共享变量的简单使用问题。这些是任何一个写多线程程序的人,都应该熟悉的最基础的问题。我们忽略了一些其他多线程实现提供的工具。它们虽然很少被用到,但是对于你的程序仍然很有必要。
其他锁类型
大多数环境提供可重入锁,即被一个单线程多次持有,比如java synchronized 块就有这种锁的特性行为。通常读写锁也提供这个功能,即一个锁可以同时被多个“读”线程持有,但只能同时被一个“写”线程持有。
条件变量等
对于一个线程等待某个特殊条件符合要求,条件变量时最普遍的机制,比如等待一个共享队列是不为空的。这些提供wait()调用来释放并且重新获取一个锁,挂起线程一段时间,直到它被另个线程唤醒(”notified” or signalled”)。如果一个线程不能被正确唤醒,条件变量很容易引入死锁到应用程序。因此它们应该被谨慎的运用。但如果我们的关注点只是我们的程序而不是产生的错误结果,一个wait(cv, lock)调用行为就如同一个unlock(lock)加lock(lock)的序列。从而新的问题与这篇文章非常正交。
非阻塞锁获取
大多数语言或者线程库提供一个trylock()原函数,可以获得一个锁,像lock()的功能,或者返回一个错误的提示,但它从来不会阻塞,这非常容易被我们的模型容纳,我们允许trylock()返回一个错误的提示,即使这个锁是可获得,或者至少是我们代码的原因,虽然这有可能发生。 因为一些微妙的原因这个假设不允许通过程序实现 ,否则可以看出顺序一致性的错觉 ,类似观察申请锁时调用超时。实际上,不允许在代码中滥用trylock(),并且应该去用其他一些原语。
从顺序一致性“逃离”
很多语言允许通过侵犯顺序一致性来获取同步变量,即使程序不包含数据竞争。很多平台用这些来显著提升性能,以一个更复杂的程序模型为代价,在这里我们不讨论该问题。例如java.util.concurrent.atomic‘s lazySet() (Java 6 +) and weakCompareAndSet() 允许一些实现以和顺序一致性违背的方式来进行重排序。C++0x atomic objects 支持内存排序限定参数的操作,它们大部分和顺序一致性违背。
历史和致谢
顺序一致性概念是被Lamport引入的,How to Make a Multiprocessor Computer that Correctly Executes Multiprocess Programs. sequential-consistency-for-data-race-free-programs方式从一开始就已经是Ada编程模型的基础。它作为一个被机器架构有效支撑的模型大约20年前被Sarita Adve研究的更详细。(See for example Adve and Hill, Weak Ordering – A New Definition, ISCA 1990.) 如我们以上所指出的,Posix线程模型至少和它相似,但是很多Posix使用者不知道这一点。我们相信windows native 线程模型也是取向和它类似,但是直到最近还没有关于该主题的很多讨论。
最近几年java和C++0x内存模型是明确的基于这种方式实现的。除了这些方面的论文作者,还有主要的贡献者包括Lawrence Crowl, Doug Lea, Paul McKenney, Clark Nelson, Bratin Saha, and Herb Sutter.
Paul McKenney也发表了一篇“frequently asked questions”文章,内容和这篇文章类似。Sarita Adve在实现章节提供一些内容。我们也借用了Herb Sutter例子。
很多读者在早期的草稿中提供了有用的评论。Rob Schreiber提供了特殊的扩展。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/119728.html