【问题标题】:Starting c++11 thread with a lambda capturing local variable使用捕获局部变量的 lambda 启动 c++11 线程
【发布时间】:2025-12-28 12:00:11
【问题描述】:

我有一个相当基本的问题,但不确定它的来源:并发环境中的 lambda 捕获评估或滥用文件系统库。
这是示例代码:

#include <iostream>
#include <vector>
#include <thread>
#include <boost/filesystem.hpp>

using namespace std;
using namespace boost::filesystem;

void query(const string filename)
{
    cout << filename << " ";
}

int main() {
    path p("./");
    vector<thread> thrs;

    for(auto file = directory_iterator(p); file != directory_iterator(); ++file)
    {
        thread th( [file] {query(file->path().string());} );
        thrs.push_back(move(th));
    }

    for (auto& t : thrs)
        t.join();

    return 0;
}

在运行时给出:

:~/workspace/sandbox/Release$ l
main.o  makefile  objects.mk  sandbox*  sources.mk  subdir.mk
:~/workspace/sandbox/Release$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/a/custom-libs/boost-1_59/lib/ ./sandbox 
./subdir.mk ./sources.mk ./sandbox ./objects.mk ./main.o ./main.o

注意竞争条件 - 并非所有文件最终都被传递给线程函数(在此运行时 makefile 丢失)。
我可以通过在本地变量中提取参数来找到解决方法,将循环体重写为:

    auto fn = file->path().string();
    thread th( [fn] {query(fn);} );
    thrs.push_back(move(th));

竞态条件从何而来?
file->path().string() 不是在创建线程的时候进行评估吗?

【问题讨论】:

  • 所有线程同时使用 std::cout ...

标签: multithreading c++11 boost


【解决方案1】:

file->path().string() 不是在创建线程的时候进行评估吗?

没有。由于必须在新线程上调用query,因此必须在新线程上执行语句query(file-&gt;path().string())。所以它是在线程创建后的一段时间内执行的,此时线程开始做事。

你捕获了file。期间。

在概念上与:

string * j = new string ("hello");
thread th( [j] { cout << *j << std::endl; } );
*j = "goodbye";
th.join();

你捕获了j。不是*j。虽然j 的值不会改变,但j 所指事物的值会发生变化。所以谁知道当线程最终取消引用它时会是什么。

您可能认为您正在捕获迭代器的值,因此您会没事的,因为该值不会改变。不幸的是,这不是这个迭代器的实现方式。它的实现方式允许它在递增时不可挽回地丢弃以前的信息,因此递增这种类型的迭代器也会增加该迭代器的副本。

如果您捕获的某物的值引用了您未捕获的值,如果第二个值发生了变化,那么您现在捕获的东西引用了不同的值。始终准确地知道您正在捕捉什么以及如何捕捉它。遗憾的是,您无法安全地按值捕获您不深入理解的类的实例。

【讨论】:

    【解决方案2】:

    线程的 lambda 函数中的代码不会在创建线程时进行评估,而是在线程执行时进行。这可能是创建线程后的任意时间,具体取决于操作系统调度。

    真正不行的原因是因为a directory_iterator is a single pass iterator,意思是不能复制,提前一份,再用旧的副本。通过捕获,您正是在这样做。您可以修改程序以在创建线程之前提取路径并捕获它:

    int main() {
        path p("./");
        vector<thread> thrs;
    
        for(auto file = directory_iterator(p); file != directory_iterator(); ++file)
        {
            thrs.emplace_back([path = file->path()] {query(path.string());});
        }
    
        for (auto& t : thrs)
            t.join();
    
        return 0;
    }
    

    【讨论】: