2

我正在尝试使用多线程方法进行递归目录列表。以下代码在将异步调用替换为普通的单线程递归函数调用时工作正常,但是当使用异步实现时,当从 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 将抛出异常并停止,如果您没有读取权限,则无法继续,因此此方法应允许您在这种情况下继续。

4

1 回答 1

2

问题是基于范围的 for 循环。

如果我们看一下基于范围的 for 语句是如何定义的,我们会发现循环的结束迭代器只会被计算一次。在进入循环时,您的向量中可能只有一个未来(这是一场比赛)(您在上面的行中推回的那个)。因此,在该任务完成后,迭代器将递增并等于您的旧结束迭代器,并且即使向量现在可能包含更多在您的第一个任务中被推回的元素,循环也将完成。这还有更多的问题。

将在完成循环后调用的向量的析构函数通常应该调用其所有元素的析构函数,这对于将来的 fromstd::async将等于调用等待,尽管当向量已经在其析构函数中时,您仍在向向量添加元素,这可能是UB。

另一点是,当您在第一个线程中 push_back 到向量时,您在进入 for 循环时创建的结束迭代器将失效,这意味着您正在对失效的迭代器进行操作。

作为一种解决方案,我建议避免使用全局任务列表,而是在您的函数中使用本地任务列表,然后您可以在每个级别的函数searchFiles中等待所有本地未来。searchFiles这是非托管递归并行中的常见模式。

注意:我不知道 ppl concurrent_vector 的所有细节,但我认为它的行为类似于std::vector.

于 2013-06-05T09:47:16.507 回答