标准C++的世界是相当保守和陈旧的。在这个纯洁的世界,所有可执行文件都是静态链接的。不存在内存映射文件和共享内存。没有窗口系统,没有网络,没有数据库,没有其他进程。在这种情况下,当发现标准没有提到任何关于线程的东西时你不该感到惊讶。你对STL的线程安全有的第一个想法应该是它将因实现而不同。
在STL容器里对多线程支持的黄金规则已经由SGI定义:
1. 多个读者是安全的。多线程可能同时读取一个容器的内容,这将正确的执行,
当然,在读取时不能有任何写入者操作这个容器;
2. 对不同容器的多个写入者是安全的。多线程可以同时写不同的容器。
一个库可能试图以下列方式实现线程安全的容器:
1. 每次调用容器的成员函数期间都要锁定该容器;
2. 每个容器返回的迭代器的生存期之内都要锁定该容器;
3. 在每个容器上调用算法执行期间锁定该容器。
上面列举的锁定方法并不能防止下面代码的问题
1 vector<int> v; 2 vector<int> :: iterator first5( find( v.begin(), v.end(), 5 ) ); // 行1 3 if( *first5 != v.end() ) // 行2 4 *first5 = 0; // 行3
例如,另一个线程在行1完成之后立刻修改 v 中的数据,导致行2、行3不能正常运行
为了代码成为线程安全的,v必须从行1到行3保持锁定,手工同步控制
1 vector<int> v; 2 ... 3 getMutexFor(v); 4 vector<int> :: iterator first5( find( v.begin(), v.end(), 5 ) ); 5 if( *first5 != v.end() ) // 这里现在安全了 6 *first5 = 0; // 这里也是 7 8 releaseMutexFor(v);
一个更面向对象的解决方案是创建一个Lock类,在构造函数里获得互斥量并在析构函数里释放它,
这样使 getMutexFor 和 releaseMutexFor的调用不匹配的机会减到最小:
1 template<typename Container> // 获取释放容器的互斥量的类的模板核心 2 class Lock 3 { 4 public: 5 Lock(const Containers container) : c(container) 6 { getMuxtexFor(c); } // 在构造函数获取互斥量 7 ~Lock() 8 { releaseMuxtexFor(c); } // 在析构函数里释放它 9 private: 10 const Container & c; 11 };
使用Lock类来获取互斥量:
1 vector<int> v; 2 ... 3 { // 建立新块 4 Lock< vector<int> > lock(v); // 获取互斥量 5 vector<int> :: iterator first5( find( v.begin(), v.end(), 5 ) ); 6 if( *first5 != v.end() ) 7 *first5 = 0; 8 9 } // 关闭块,自动释放互斥量
建立一个里面定义了Lock的新块,当不再需要互斥量时就关闭那个块
另外,Lock的方法在有异常的情况下也是文件的。而 getMutexFor 与 releaseMutexFor 之间有异常抛出,将不会释放互斥量。
当涉及到线程安全和STL容器时,可以确定库实现允许在一个容器上的多个读者和不同容器的多写入者。