Effective STL 学习笔记: Thread Safety and STL Container
Table of Contents
1 STL, Thread and SGI
C++98 以及之前的 C++ 标准中,并未对线程做出过规定,但对于 STL 来讲,SGI 做出了自己的规定,很多其他的 STL 实现也遵循这些规定:
The SGI implementation of STL is thread-safe only in the sense that simultaneous accesses to distinct containers are safe, and simultaneous read accesses to to shared containers are safe. If multiple threads access a single container, and at least one thread may potentially write, then the user is responsible for ensuring mutual exclusion between the threads during the container accesses.
This is the only way to ensure full performance for containers that do not need concurrent access. Locking or other forms of synchronization are typically expensive and should be avoided when not necessary.
It is easy for the client or another library to provide the necessary locking by wrapping the underlying container operations with a lock acquisition and release. For example, it would be possible to provide a locked_queue container adapter that provided a container with atomic queue operations.
For most clients, it would be insufficient to simply make container operations atomic; larger grain atomic actions are needed. If a user's code needs to increment the third element in a vector of counters, it would be insuffcient to guarantee that fetching the third element and storing the third element is atomic; it is also necessary to guarantee that no other updates occur in the middle. Thus it would be useless for vector operations to acquire the lock; the user code must provide for locking in any case.
This decision is different from that made by the Java designers. There are two reasons for that. First, for security reasons Java must guarantee that even in the presence of unprotected concurrent accesses to a container, the integrity of the virtual machine cannot be violated. Such safety constraints were clearly not a driving force behind either C++ or STL. Secondly, performance was a more important design goal for STL than it was for the Java standard library.
On the other hand, this notion of thread-safety is stronger than that provided by reference-counted string implementations that try to follow the CD2 version of the draft standard. Such implementations require locking between multiple readers of a shared string.
2 STL and Lock
2.1 RAII
需要对一个容器进行写操作的时候,我们可以加锁保护:
1: // skeletal template for classes that acquire and release mutexes for containers; many 2: // details have been omitted 3: template<typename Container> class Lock 4: { 5: public: 6: Lock(const Containers container) : c(container) 7: { 8: getMutexFor(c); // Acquire mutex in constructor 9: } 10: ~Lock() 11: { 12: releaseMutexFor(c); // Release mutex in constructor 13: } 14: private: 15: const Container& c; 16: };
这里用到了一个技巧 Resource Acquisition Is Initialization ( RAIII ) ,其基本思想包括:
- 所用资源在对象构造时就初始化好
这样可以保证用到该对象时,所需访问的资源始终为 Valid 状态。 - 所用资源在对象析构时释放
这样就保证了资源可以自动释放。
2.2 Use Lock in STL
17: vector<int>v; 18: { 19: Lock<vector<int> > lock(v); // Lock initialized here. 20: vector<int>::iterator frist5(find(v.begin(), v.end(), 5)); 21: if (first5 != v.end()) 22: { 23: *first5 = 0; 24: } 25: } // Lock will be released after exit this scope.
(使用许可:署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议 。)