【翻译】设计线程安全的数据结构的几条准则

译者注:这篇文章来自《C++ concurrency in action》chapter 6.2, 仅供学习参考

就像我之前提到的,在设计支持并发访问的数据结构时往往有两方面需要考虑:确保这些访问是安全的,同时确保这个数据结构支持真正的并发访问。使数据结构线程安全化的一些基础知识在第三章中讲过了。

有的状态的不变性会被其他线程的行为打破,确保没有线程会遇到这样的状态。
通过提供完成整步操作而不是分步操作的函数作为接口,来避免数据结构的接口可能导致的潜在的竞态条件。
通过留意数据结构在出现异常的表现来确保不变性不会被打破。
压缩上锁的范围,避免嵌套锁的使用,这样可以减小出现死锁的可能性。

但是,在你考虑以上几点之前先思考你对用户对使用这个数据结构的要求和限制,这很重要。
如果一个线程通过一个特定的函数访问了这个数据结构,那么其他线程访问哪些函数时是线程安全的呢?

实际上这是一个值得考虑的关键问题。大部分构造器和析构器都要求调用者对数据结构的互斥访问,但是这个条件是否成立取决于用户。如果一个数据结构支持赋值函数、swap函数和拷贝构造函数,那么作为这个数据结构的设计者你需要考虑这些函数在并发访问时是否安全,或者说你是否要求用户确保对这些函数的调用时互斥的,甚至你还要考虑对其他大部分对这个数据结构进行操作的函数的并发调用是否会出现问题。

第二个需要考虑的方面是,你的数据结构是否支持真正的并发访问。关于这方面内容我没法在这篇准则中说了,不过这里可以提供一个问题列表,作为一个数据结构设计者你应该问问自己这些问题。
上锁的区域是否可以缩小?这样就可以让更多的操作在不上锁的情况下进行。
数据结构的不同部分是否使用的不同的锁来保护?
这些所有的操作都需要相同级别的保护吗?
可不可以通过一些微小的改动,在不影响操作的语义的情况提高数据结构的并发性?
所有这些问题都要遵循一条简单的准则:我们如何减少需要串行的操作来提高数据结构真正的并发性?
允许读取数据操作可以由多个线程同时进行,而修改操作必须由单个线程互斥进行的数据结构是很常见的,这个特性可以通过使用一些特殊结构比如boost::shared_mutex来实现。
同样,我们即将看到,支持不同的操作同时进行而相同的操作只能串行进行的数据结构也是很常见的。

最简单的线程安全数据结构通常使用互斥量或锁来保护数据————虽然就像我们在第三章中看到的那样,使用这种方法有许多争议,但是相对来说,使用这种方法来确保对数据结构的互斥访问仍然是比较简单的。为了使我们的工作更轻松一点,我们在这个章节仍然会讨论基于锁的数据结构设计,无锁的数据结构设计将留在第七章讲解。