在为工作线程访问 lambda 中捕获的向量列表中的元素引用时,是否需要互斥锁?

Is a mutex required when accessing references for elements in a list of vectors captured in a lambda for a worker thread?

本文关键字:是否 引用 列表 访问 线程 工作 lambda 向量 元素      更新时间:2023-10-16

我有以下代码:

#include <vector>
#include <thread>
#include <boost/range/irange.hpp>
...
{
using namespace std;
unsigned cpus = 8; // number of threads
vector<vector<uint64_t>> aprimes(cpus);
vector<thread> workers;
for(int cpu: boost::irange(cpus)){
vector<uint64_t>& tprimes = aprimes[cpu];
workers.push_back(thread([=, &tprimes](){
// top work on tprimes
tprimes.push_back(5);
}));
}
for_each(workers.begin(), workers.end(), [](thread &t)
{
t.join();
});
for(auto vec: aprimes){
for(int val: vec){
cout << val << endl;
}
}
}

我正在为每个线程中使用的 lambda 捕获对每个向量 (tprimes) 的向量 aprimes 列表中的引用。 这种捕获安全吗? 我应该在这里使用互斥锁还是其他访问方法? 代码确实有效,但我不确定以后是否会失败。

是的,这是安全的。 当我们谈论数据竞争和未定义的行为时,我们谈论的是修改同一共享对象的多个线程。 在您的情况下,您没有这个。aprimes的每个元素都是它自己独特的对象,因此每个工作线程都在处理一个对象,只有它正在访问。 即使它们都碰巧在里面,只要你不修改aprimes也不会改变这一点aprimes. 在这种情况下,就像一个接一个地声明一堆向量一样。

唯一需要注意的是,您的主线程不允许修改aprimes,也不允许修改aprimes中的任何元素。 如果这样做,那么您将有多个线程写入共享对象而不同步,这是数据争用和未定义的行为。


请注意,执行此操作时可能发生的一件事是错误共享。 如果你有一个包含 4 个元素的vector<int>,并且每个线程都得到一个整数,那么即使没有线程共享对象,它们也会共享对象所在的缓存行。 这会导致 CPU 在其中一个整数更新时不断必须同步 CPU 缓存,因为它只能在缓存行级别工作。 这有效地使程序像没有线程一样运行,因为所有 4 个线程都不能同时修改缓存行。 在这种情况下,由于我们正在处理向量,因此它应该不是问题,但这是需要注意的事情。

只要aprimes向量不改变(添加内容),它就不会重新分配。

只要它不重新分配,各种tprimes向量就不会四处移动。每个tprime在内存中都有自己的位置。每个线程将从不同的向量读取和写入。

因此,它是安全的。