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

Posted

技术标签:

【中文标题】访问工作线程的 lambda 中捕获的向量列表中元素的引用时是不是需要互斥锁?【英文标题】:Is a mutex required when accessing references for elements in a list of vectors captured in a lambda for a worker thread?访问工作线程的 lambda 中捕获的向量列表中元素的引用时是否需要互斥锁? 【发布时间】:2019-07-17 18:07:53 【问题描述】:

我有以下代码:

#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;
        
    

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

【问题讨论】:

为什么irange 上的这个复杂的 for 循环?只是为了复杂化? @Slava 因为我更喜欢它,而且我可以做这样有趣的事情:for(const unsigned cpu: boost::irange(cpus)) 以进一步表明应该如何使用该值的意图。我真的应该在这篇文章的代码中使用它来对其他开发人员说“嘿,不要乱用这个索引,嗯,凯?”。还有,因为我更喜欢。我很喜欢。此外 ++i 是 1990 年代... ;-) 我不是说使用旧的好循环。我的观点是你过于复杂简单的事情。循环的前 2 行可以替换为:for( vector&lt;uint64_t&gt;&amp; tprimes : aprimes ) ... 甚至 for ( auto &amp;tprimes : aprimes ) ... @Slava 我将此代码从工作代码中提取出来,以询问有关我如何引用 aprimes 列表中的向量的具体问题。我实际上在 lambda 中使用了 cpu 变量。这未显示,因为它与问题无关。是的,循环遍历 aprimes 向量也是一个不错的解决方案。 【参考方案1】:

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

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


请注意,这样做时可能发生的一件事是false sharing。如果您有一个带有 4 个元素的 vector&lt;int&gt;,并且每个线程都获得一个整数,那么即使没有线程共享该对象,它们也会共享该对象所在的缓存行。这会导致 CPU 不断地拥有在更新其中一个整数时同步 CPU 缓存,因为它只能在缓存行级别上工作。这将有效地使程序像没有线程一样运行,因为所有 4 个线程都不能同时修改缓存行。在这种情况下,因为我们正在处理向量,所以这应该不是问题,但需要注意。

【讨论】:

【参考方案2】:

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

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

因此,它是安全的。

【讨论】:

以上是关于访问工作线程的 lambda 中捕获的向量列表中元素的引用时是不是需要互斥锁?的主要内容,如果未能解决你的问题,请参考以下文章

使用lambda函数进行向量排序,当不在同一范围内时如何传递变量来捕获组?

是否可以提取 lambda 的捕获列表?

lambda表达式

如何访问列表中元组的第一个元素?

如何修改 lambda 中的字符串向量

捕获列表中的 C++ lambda 复制值