想知道以下列方式迭代 STL 容器(例如向量)是否安全,以避免锁定读/写,但只允许任何“写入”线程进行 push_back() 操作。
for (size_t i = 0; i < vec.size(); i++)
{
const T& t = *vec[i];
// do something with t
}
我知道迭代器可能会因容器的更改而失效,但也许如果我们确保初始容器大小足够大以供将来添加,那么在不锁定读取或写入的情况下迭代元素也应该是安全的?
想知道以下列方式迭代 STL 容器(例如向量)是否安全,以避免锁定读/写,但只允许任何“写入”线程进行 push_back() 操作。
for (size_t i = 0; i < vec.size(); i++)
{
const T& t = *vec[i];
// do something with t
}
我知道迭代器可能会因容器的更改而失效,但也许如果我们确保初始容器大小足够大以供将来添加,那么在不锁定读取或写入的情况下迭代元素也应该是安全的?
想知道以下列方式迭代 STL 容器(例如向量)是否安全,以避免锁定读/写,但只允许任何“写入”线程进行 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
缓冲区元素的构造函数。如果发生这种重新排序,那么您的阅读器线程可能会看到一个size()
包含当前存储的值的值,但该值尚未在向量中。
据我所知,您不应该编写代码来依赖它作为实现细节,并且它不是向量导出 API 的一部分。如果它有记录的行为,你可以依赖它,如果不是,那么不要这样做。任何依赖于实现而不是 API 记录部分的内容都可能在不同平台和同一平台上不同版本的工具上发生变化。
此外,来自@GManNickG 的评论-> 您将在调用 size() 时遇到竞争条件,因为它将被修改和读取而不会在此处锁定。
你不能依赖关于迭代器不会失效的建议(因为还有很多空间)。为此,您需要 shared_mutex 和 shared_lock 。(使用示例)