我正在尝试使用多线程方法执行递归目录列表。将async调用替换为普通单线程递归函数调用时,下面的代码工作正常,但是当使用async实现时,递归启动的线程似乎在从main完成初始异步调用时终止,因为输出显示了对函数的多次调用启动但是输出所有文件的唯一目录是初始目录,“完成”只输出一次,尽管“已启动”输出多次,并且还输出一些其他目录的文件。我怀疑我遗漏了一些基本的东西。任何人都可以解释这段代码有什么问题吗?
#include <filesystem>
#include <future>
#include <functional>
#include <concurrent_vector.h>
#include <concurrent_queue.h>
#include <iostream>
using namespace std;
using namespace std::tr2::sys;
using namespace concurrency;
concurrent_vector<future<void>> taskList;
void searchFiles(wstring path, concurrent_queue<wstring>& fileList)
{
wcout << L"Started " << path << endl;
wdirectory_iterator directoryIterator(path);
wdirectory_iterator endDirectory;
for( ; directoryIterator != endDirectory; ++directoryIterator)
{
wcout << path + L"/" + (wstring)directoryIterator->path() << endl;
if ( is_directory(directoryIterator->status() ) )
{
taskList.push_back( async( launch::async, searchFiles, path +
L"/" + (wstring)directoryIterator->path(), ref(fileList) ));
}
else
{
fileList.push( path + L"/" + (wstring)directoryIterator->path() );
}
}
wcout << L"Finished " << path << endl;
}
int main()
{
concurrent_queue<wstring> fileList;
wstring path = L"..";
taskList.push_back( async( launch::async, searchFiles, path, ref(fileList) ));
for (auto &x: taskList)
x.wait();
}
有些人可能会问我为什么不使用wrecursive_directory_iterator。显然wrecursive_directory_iterator会抛出一个异常,如果你没有读取权限就停止无法继续,所以这种方法应该允许你在这种情况下继续。
答案 0 :(得分:2)
问题是基于范围的for循环。
如果我们看一下如何定义range-based for statement,我们会看到循环的结束迭代器只计算一次。在进入循环时,可能(这是一场比赛)你的向量中只有一个未来(你在上面的线中推回的那个)。因此,在该任务完成之后,迭代器将递增并且等于旧的end-iterator,并且循环将完成,即使向量现在可能包含在第一个任务中被推回的更多元素。还有更多的问题。
在完成循环后将调用的向量的析构函数通常应调用其所有元素的析构函数,对于将来std::async
将等于调用wait,尽管您仍在向元素添加元素矢量虽然它已经在它的析构函数中,这可能是UB。
另一点是,只要你在第一个线程中将push_back传递给向量,你在输入for循环时创建的结束迭代器就会失效,这意味着你在无效的迭代器上运行。
作为一种解决方案,我建议避免使用全局任务列表,而是在searchFiles
函数中使用本地任务列表,然后等待searchFiles
函数中的所有本地期货在每个级别。这是非托管递归并行性的常见模式。
注意:我不知道ppl concurrent_vector的所有细节,但我认为它的行为类似于std::vector
。