Effecitve stl 第一章 第12条

来源:互联网 发布:美国国际数据集团 编辑:程序博客网 时间:2024/06/05 16:23

第12条:切勿对STL容器的线程安全性有 不切实际的依赖

对一个STL实现最多只能期望:

1 多个线程读是安全的。多个线程可以同时读一个容器的内容,而且保证是正确的。自然地,在读的过程中,不能对容器有任何写入操作。

2 多个线程对不同的容器做写入操作时安全的。多个线程可以同时对不同的容器做写入操作。

这是所能期望的,而不是所能依赖的,有些实现提供了这些保证,有些则没有。


现在考虑下面的代码: 在一个vector<int>中查找值为5的第一个元素,如果找到了,就将它设为0

vector<int> v;

....

vector<int>::iterator first5(find(v.begin(), v.end(), 5));          //第1行

if (first5 != v.end())                                                                     //第2行

     *first5 = 0;                                                                              //第3行

以上可能发出的线程不安全有

如果在第1行刚刚完成后,另一个不同的线程会更改v中的数据,如果发生了,则第2行对first5和v.end是否相等的检查将会变得没有意义,以为v的值与第一行结束时的是不同的。

事实上,这一检查可能导致不确定的行为,因为另外一个线程可能会夹在第1行和第2行中间,使first5变得无效,

或者这第二个线程可能会执行一个插入操作使得vector重新分配他的内存(这将会使vector得所有迭代器变得无效)。

同样的,对*first5的赋值也是不安全的,因为一个线程可能在第2行和第3行之间执行,该线程可能会使first5无效,例如可能会删除它所执行的元素(或者至少是曾经指向过的元素)。


这时不能指望任何STL实现解决这个线程问题,这种情况下,应该手工做同步控制,例如下面:

方案1:

vector<int> v;

....

getMutexFor(v);

vector<int>::iterator first5(find(v.begin(), v.end(), 5));          //第1行

if (first5 != v.end())                                                                     //第2行

     *first5 = 0;                                                                              //第3行

releaseMutexFor(v);


方案2:

使用一个Lock类,面向对象的方法,在构造函数中获得一个互斥体,在析构函数中释放它,从而尽可能得减少getMutexFor调用而没有相对应的releaseMutexFor调用的可能性。

template <typename Container>          //一个为容器获取和释放互斥体的模板

class Lock

{

public:

Lock(const Container& container) :c(container)

{

         getMutexFor(c);           //在构造函数中获取互斥体

}

~Lock()

{

        releaseMutexFor(c);      //在西沟函数汇中释放它

}

private:

       const Container& c;

}

使用类来管理资源的生存期的思想通常被称为“获得资源时即初始化”,使用上面的类来管理互斥体,如下:

vector<int> v;

....

{

Lock<vector<int> > lock(v);      //获取互斥体

vector<int>::iterator first5(find(v.begin(), v.end(), 5));          //第1行

if (first5 != v.end())                                                                     //第2行

     *first5 = 0;                                                                              //第3行

}     //代码块结束,自动释放互斥体

如果没有创建代码块,也仍然会释放,只不过是当控制到达包含lock的代码块末尾时。

基于Lock的方案在有异常时也是强壮的, c++ 保证,如果有异常抛出,局部对象也会被析构,即即使当我们在使用Lock对象时又异常抛出,lock仍会释放它所有的互斥体。


0 0
原创粉丝点击