我有一个相当基本的问题,并且不确定它的来源:并发环境中的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 缺失)。
我能够通过在局部var中提取参数来找到解决方法,将循环体重写为:
auto fn = file->path().string();
thread th( [fn] {query(fn);} );
thrs.push_back(move(th));
比赛条件来自哪里?
在创建线程时是不是 file-&gt; path()。string()?
答案 0 :(得分:2)
在线程创建时,isn&lt; file&gt; path()。string()是否已被评估?
没有。由于必须在新线程上调用query
,因此必须在新线程上执行语句query(file->path().string())
。所以它在线程创建之后执行了一段时间,当线程开始做事时。
您抓获了file
。周期。
它在概念上与以下内容没有区别:
string * j = new string ("hello");
thread th( [j] { cout << *j << std::endl; } );
*j = "goodbye";
th.join();
您抓获了j
。不是*j
。虽然j
的值不会发生变化,但j
所指的事物的价值会发生变化。所以谁知道线程最终取消引用它会是什么。
您可能认为自己已经捕获了迭代器的价值,因此您可以放心,因为这个价值不会发生变化。不幸的是,这并不是这个迭代器的实现方式。它的实现方式允许它在递增时不可挽回地丢弃先前的信息,因此增加这种类型的迭代器也会增加迭代器的副本。
如果你捕捉某些东西的值,这些东西指的是你没有捕获的值,那么如果第二个值被改变,你现在捕获的东西就会引用不同的值。始终确切地知道您捕获的内容以及捕获方式。可悲的是,你不能安全地捕捉你不太了解的课程的价值实例。
答案 1 :(得分:1)
线程的lambda函数中的代码在创建线程时不会被评估,但是当线程被执行时。根据OS调度创建线程后,这可能是一个任意时间。
它不起作用的真正原因是因为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;
}