【问题标题】:C++ async threads terminate when calling thread finishesC++异步线程在调用线程完成时终止
【发布时间】:2013-06-05 02:48:40
【问题描述】:

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

【问题讨论】:

  • 我想知道你为什么要用多个线程来做这件事。这有可能使您的磁盘出现漏洞。
  • 你可能会猜到这是一个并发编程的学校项目,所以这就是使用多线程方法的重点。 Visual Studio concurrent_vector 和 concurrent_queue 容器也与问题无关。他们只是为了在我发现这个问题后对期货和找到的文件进行进一步的工作。

标签: c++ multithreading recursion c++11 stdasync


【解决方案1】:

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

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

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

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

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

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

【讨论】:

  • 我认为你是对的,但我不确定显示基于范围的 for 循环的定义会有所帮助,并且 "这意味着循环只会遍历向量中的第一个也是唯一的未来。” 可以更清楚地解释。 为什么是这个意思?
  • @Jonathan Wakely 好的,感谢您的反馈。我稍后会添加更新。
  • 非常感谢@bamboon 帮助我解决了我的问题。我在这个上用头撞墙了几个小时。我应该已经看到,在确保完成填充之前开始等待期货向量是一个逻辑错误。为了解决这个问题,我添加了一个原子计数器,当我异步调用函数时它会递增,并在它退出之前递减。这让我解决了第二个问题,即如何确定我已经完成了,因为我实际上并不想等待期货,而是开始弹出队列并处理它。
  • 顺便说一句,我会投票,但我没有这样做的声誉。我已经潜伏了很长时间,但这是我的第一篇文章。
猜你喜欢
  • 2017-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-22
  • 1970-01-01
  • 2011-04-19
相关资源
最近更新 更多