我可以让一个线程保存对 std::deque 第一个元素的引用(或迭代器),而另一个线程在后面添加/删除元素,但从不接触第一个元素吗?
我知道我可以使用互斥锁来锁定整个数据结构,但我想知道,因为在后面添加/删除元素永远不会使前面的迭代器无效(假设后面!=前面),如果这样做可以工作。
编辑
下面的示例显示了 Mac 操作系统上 clang-15 的竞争条件(断言失败)。有趣的是,相同的示例但插入/删除在后面完成并且元素在前面读取似乎可行。
#include <deque>
#include <thread>
#include <cassert>
#include <chrono>
int main()
{
std::deque<int> deque;
deque.push_back(42);
auto thread1 = std::thread([&](){
for (auto i = 0u; i < 1000000000; ++i)
{
auto& j = deque.back();
using namespace std::chrono_literals;
std::this_thread::sleep_for(10ms);
assert(j == deque.back());
}
});
auto thread2 = std::thread([&](){
for (auto i = 0u; i < 1000000000; ++i)
if (deque.size() == 1)
deque.push_front(43);
else
deque.pop_front();
});
thread1.join();
thread2.join();
}
最佳答案
所显示的程序不保证不存在数据争用,因此具有未定义的行为。请参阅此答案的结尾。
Could I let one thread hold a reference (or iterator) to the first element of a std::deque while another is adding/removing elements at the back, but never touching the first element?
是的,访问标准库容器的不同元素本身不会导致数据争用(每个元素位于不同的内存位置)以及从 std::deque< 的开头或结尾删除/添加元素
不会使对其他元素的引用无效,也不允许容器的成员函数接触容器的元素(除非其规范要求)。
当然,您不能删除引用的对象,也不能让两个线程不同步地访问容器的同一元素。
此外,当使用 .erase
/.emplace
/etc 来删除/添加 std::deque 开头或结尾以外的元素时
code> 或者如果使用 shrink_to_fit
,那么所有引用都会失效,并且在单线程场景中已经禁止使用该引用。
对于迭代器来说,任何添加元素的操作(无论是否在末尾)都会使它们失效。在这种情况下,无论是单线程还是多线程,它也是 UB。
此外,当容器(可能)不同步修改时(因为迭代器对容器具有只读访问权限),无论是添加还是删除元素,都不能保证访问/修改迭代器不会发生数据争用。这适用于所有标准库容器。
以上所有内容仅适用于存储和重用引用/迭代器。当在一个线程中调用修改成员函数与在另一个线程中调用任何成员函数不同步时,永远不能保证不存在数据争用,正如您在例如,在第一个线程中调用 back
。
关于c++ - std::deque 的线程安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75749818/