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

Iterate over STL container using indices safe way to avoid using locks?

本文关键字:方式 安全 索引 迭代 STL      更新时间:2023-10-16

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

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

我知道迭代器可能会因容器的更改而失效,但如果我们确保初始容器大小足够大,可以容纳任何未来的添加,那么在不锁定读或写的情况下迭代元素也应该是安全的?

想知道以以下方式迭代STL容器(如vector)是否安全,以避免在读/写时锁定,但只允许任何"写"线程进行push_back()操作。

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

将问题限制在reserve() -ed向量上(即避免增加缓冲区的问题),该方法仍然不是线程安全的。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的构造函数在缓冲区元素。如果发生了重新排序,那么reader线程可能会看到包含当前存储的值的size()值,但该值尚未在vector中。

你不应该写代码依赖于它的实现细节,而不是矢量的导出API的一部分,据我所知。如果它是有文档记录的行为,你可以依赖它,如果不是,那就不要这样做。任何依赖于实现而非API文档部分的内容都可能在不同的平台上以及同一平台上工具的不同版本上发生变化。

同样,从@GManNickG的评论->你将有一个竞争条件在size()的调用,因为它将被修改和读取没有锁定在这里。

不能依赖关于迭代器不会失效的建议(因为还有很多空格)。你需要shared_mutex和shared_lock。(用法示例)