2

マルチスレッドのアプローチを使用して、再帰的なディレクトリ リストを作成しようとしています。次のコードは、非同期呼び出しを通常のシングル スレッドの再帰関数呼び出しに置き換えると正常に動作しますが、非同期で実装すると、再帰的に開始されたスレッドはすべて、main から行われた最初の非同期呼び出しが終了したときに終了するように見えます。ただし、すべてのファイルが出力されるディレクトリは最初のディレクトリのみであり、「開始」は数回出力され、他のディレクトリのファイルも出力されますが、「終了」は 1 回だけ出力されます。私は根本的な何かが欠けていると思います。このコードの何が問題なのか説明できる人はいますか?

#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 ステートメントがどのように定義されているかを見ると、ループの終了反復子が 1 回だけ計算されることがわかります。ループに入る時点で、おそらく (これはレースです) ベクトルには 1 つの未来しかありません (上記の行で押し戻したもの)。したがって、そのタスクが終了すると、イテレータはインクリメントされ、古い終了イテレータと等しくなり、最初のタスクでプッシュバックされた要素がベクトルに含まれるようになったとしても、ループは終了します。これにはさらに多くの問題があります。

ループの終了後に呼び出されるベクターのデストラクタは、通常、そのすべての要素のデストラクタを呼び出す必要があります。このデストラクタは、将来的にはstd::async待機を呼び出すのと同じですが、すでにデストラクタにある間にベクターに要素を追加しているにもかかわらず、これはおそらくUBです。

もう 1 つのポイントは、for ループに入ったときに作成した end-iterator は、最初のスレッドで vector に push_back するとすぐに無効になるということです。これは、無効化されたイテレータで操作していることを意味します。

解決策として、グローバル タスク リストを回避し、代わりに関数でローカル タスク リストを使用することを提案します。その後、各レベルの関数でsearchFilesすべてのローカル先物を待つことができます。searchFilesこれは、管理されていない再帰的並列処理の一般的なパターンです。

注:pplのconcurrent_vectorの詳細をすべて知っているわけではありませんが、.に似た動作をすると思いstd::vectorます.

于 2013-06-05T09:47:16.507 に答える