使用索引安全方式迭代STL容器以避免使用锁?

时间:2013-03-12 19:42:49

标签: c++ performance stl locking

想知道以下列方式迭代STL容器(如向量)是否安全,以避免锁定读/写但只允许任何“写入”线程执行push_back()操作。

for (size_t i = 0; i < vec.size(); i++)
{
   const T& t = *vec[i];
   // do something with t
}

我理解迭代器可以通过更改容器而失效,但是如果我们确保初始容器大小足够大以便将来添加,那么在不锁定读取或写入的情况下迭代元素也应该是安全的吗? / p>

3 个答案:

答案 0 :(得分:3)

  

想知道以下列方式迭代STL容器(如向量)是否安全,以避免锁定读/写但只允许任何“写入”线程执行push_back()操作。

不,这不是线程安全的。考虑一个尝试读取值并转到当前缓冲区的线程。此时第二个线程正在增长缓冲区,并且在第一个线程获得指向缓冲区的指针之后,但在它实际读取该值之前,第二个线程释放缓冲区的内容。第一个线程是读取死对象,这是未定义的行为。

将问题限制为reserve() - 向量(即避免增加缓冲区的问题),该方法仍然不是线程安全的。 push_back()中的代码将执行与以下内容类似的操作:

template <...>
class vector {
   T *begin,*end,*capacity;   // common implementation uses 3 pointers

   void push_back(T value) {
       if (end == capacity) { /* grow vector and insert */ }
       else {
          new (end) T(value);
          ++end;
       }
   }
};

这里的问题是没有同步机制,编译器可以重新排序指令,递增end并将其存储到内存,然后通过缓冲区元素调用T的构造函数。如果发生重新排序,那么您的读者线程可能会看到size()的值,其中包含当前存储的值,但该值尚未存在于向量中。

答案 1 :(得分:0)

据我所知,你不应该编写依赖于它的代码作为它的实现细节,而不是vector的导出API的一部分。如果记录下来的行为,你可以依赖它,如果它没有那么做。依赖于实现而不是API的文档部分的任何内容都可能在同一平台上的不同平台和不同版本的工具上发生变化。

另外,来自@ GManNickG的评论 - &gt;你将在调用size()时遇到竞争条件,因为它将在不锁定的情况下进行修改和读取。

答案 2 :(得分:0)

你不能依赖关于迭代器不会失效的建议(因为剩下很多空间)。你需要shared_mutex和shared_lock。 (example of usage