目录
1.什么是线程安全?
1.什么是线程安全?
At the basic level, designing a data structure for concurrency means that multiple
threads can access the data structure concurrently, either performing the same or distinct operations, and each thread will see a self-consistent view of the data structure.
No data will be lost or corrupted, all invariants will be upheld, and there’ll be no problematic race conditions. Such a data structure is said to be thread-safe.
如何设计一个线程安全的数据结构呢?
At the basic level, designing a data structure for concurrency means that multiple
threads can access the data structure concurrently, either performing the same or distinct operations, and each thread will see a self-consistent view of the data structure.
No data will be lost or corrupted, all invariants will be upheld, and there’ll be no problematic race conditions. Such a data structure is said to be thread-safe. In general, a
data structure will be safe only for particular types of concurrent access. It may be possible to have multiple threads performing one type of operation on the data structure
concurrently, whereas another operation requires exclusive access by a single thread.
Alternatively, it may be safe for multiple threads to access a data structure concurrently if they’re performing different actions, whereas multiple threads performing the
same action would be problematic.
多个线程执行相同的操作,比如读操作,可以同时执行. 而有些操作, 比如写, 则只能同时只有一个线程执行.
另外有这么一种情况, 如果多个线程同时在同一个数据结构上执行不同的操作是安全的, 比如他们修改不同的结点的值(不是指针之类的,仅仅是值), 而针对同一个节点的操作则会有竞争问题.
Truly designing for concurrency means more than that, though: it means providing the opportunity for concurrency to threads accessing the data structure. By its very
nature, a mutex provides mutual exclusion: only one thread can acquire a lock on the
mutex at a time. A mutex protects a data structure by explicitly preventing true concurrent access to the data it protects.
简单的锁可以实现伪并发, 即同一个时间只有一个线程访问这个数据结构.
This is called serialization: threads take turns accessing the data protected by the
mutex; they must access it serially rather than concurrently. Consequently, you must
put careful thought into the design of the data structure to enable true concurrent
access. Some data structures have more scope for true concurrency than others, but in
all cases the idea is the same: the smaller the protected region, the fewer operations
are serialized, and the greater the potential for concurrency.
Before we look at some data structure designs, let’s have a quick look at some simple guidelines for what to consider when designing for concurrency.
锁实现的是一个串行. 不同数据结构的并发能力不同, 比如栈的并发服务能力就比链表的并发服务能力差(因为它只有一个入口, 这是其定义决定的).
所谓的并发的原则是这样的, 尽可能的缩小锁的粒度, 尽可能的方法并发的数量.
As I just mentioned, you have two aspects to consider when designing data structures
for concurrent access: ensuring that the accesses are safe and enabling genuine concurrent access. I covered the basics of how to make the data structure thread-safe back in
chapter 3:
■ Ensure that no thread can see a state where the invariants of the data structure
have been broken by the actions of another thread.
保证并发中的事务, 其他线程中对数据结构的破坏不能被其他线程看到.
■ Take care to avoid race conditions inherent in the interface to the data structure
by providing functions for complete operations rather than for operation steps.
提供完整的操作来防止竞争, 而非提供有可能导致竞争的接口, 比如恶心人的操作 void stack.pop(); 和 T stack.top();
■ Pay attention to how the data structure behaves in the presence of exceptions to
ensure that the invariants are not broken.
提供异常安全, 支持异常回滚, 保护数据结构的完整性(尽量而非绝对, 因为绝对是不可能的).
■ Minimize the opportunities for deadlock when using the data structure by
restricting the scope of locks and avoiding nested locks where possible.
Before you think about any of these details, it’s also important to think about what
constraints you wish to put on the users of the data structure; if one thread is accessing
the data structure through a particular function, which functions are safe to call from
other threads?
缩小锁的粒度, 防止死锁. 一个现实的问题是, 接口(方法)之间的竞争性, 如果一个接口被一个线程使用, 那么另外一个接口是否要被阻塞?
This is actually quite a crucial question to consider. Generally constructors and
destructors require exclusive access to the data structure, but it’s up to the user to
ensure that they’re not accessed before construction is complete or after destruction
has started. If the data structure supports assignment, swap(), or copy construction,
then as the designer of the data structure, you need to decide whether these operations are safe to call concurrently with other operations or whether they require the
user to ensure exclusive access even though the majority of functions for manipulating
the data structure may be called from multiple threads concurrently without problem.
举个例子吧, 一般来说, 一个数据结构的构造函数和析构函数必须是保证串行的. 他们完成前, 用户不应该访问相应的数据结构.
如果这个数据结构支持赋值, 交换, 拷贝构造, 那么你必须考虑这些操作是否允许和其他一般操作或者这些操作间的互斥与否, 是否需要串行.
The second aspect to consider is that of enabling genuine concurrent access. I
can’t offer much in the way of guidelines here; instead, here’s a list of questions to ask
yourself as the data structure designer:
■ Can the scope of locks be restricted to allow some parts of an operation to be
performed outside the lock?
■ Can different parts of the data structure be protected with different mutexes?
■ Do all operations require the same level of protection?
■ Can a simple change to the data structure improve the opportunities for concurrency without affecting the operational semantics?
几个必须考虑的问题, 能够把锁的范围尽量缩小? 把某些操作放到锁外面去?
能否设计成不同段的数据被不同的锁保护, 这样就可以提高并发?
所有的操作的保护等级是一样的吗?
能否就细节提出修改而提高并发量, 却不影响原有的含义? (最低成本提高并发性能)
All these questions are guided by a single idea: how can you minimize the amount of
serialization that must occur and enable the greatest amount of true concurrency? It’s
not uncommon for data structures to allow concurrent access from multiple threads
that merely read the data structure, whereas a thread that can modify the data structure must have exclusive access. This is supported by using constructs like boost::shared_mutex. Likewise, as you’ll see shortly, it’s quite common for a data structure to
support concurrent access from threads performing different operations while serializing threads that try to perform the same operation.