2017-02-17 5 views
2

std::forward_list共有では、同じ位置イテレータで決して呼び出さないことが保証されている場合、複数のスレッドが同時にinsert_afterを呼び出すことは安全ですか?挿入が他のイテレータを無効にしないことが保証され、コンテナにはsize()メソッドがありませんが、おそらく私は何かを見逃していることを考えれば、これは安全かもしれないようです。std :: forward_list :: insert_afterスレッドセーフ

編集:

私はロックせずにクランで正常に動作すると思われる小さな拷問テストプログラムを書いた:

#include <forward_list> 
#include <iostream> 
#include <thread> 
#include <vector> 

using List = std::forward_list<int>; 
using It = List::const_iterator; 

void insertAndBranch (List& list, It it, int depth) 
{ 
    if (depth-- > 0) { 
     It newIt = list.insert_after (it, depth); 
     std::thread thread0 ([&]{ insertAndBranch (list, it, depth); }); 
     std::thread thread1 ([&]{ insertAndBranch (list, newIt, depth); }); 
     thread0.join(); 
     thread1.join(); 
    } 
} 

int main() 
{ 
    List list; 
    insertAndBranch (list, list.before_begin(), 8); 

    std::vector<It> its; 
    for (It it = list.begin(); it != list.end(); ++it) { 
     its.push_back (it); 
    } 
    std::vector<std::thread> threads; 
    for (It it : its) { 
     threads.emplace_back ([&]{ list.insert_after (it, -1); }); 
    } 
    for (std::thread& thread : threads) { 
     thread.join(); 
    } 

    for (int i : list) { 
     std::cout << i << ' '; 
    } 
    std::cout << '\n'; 
} 

私は、これは何も証明していませんが、それはこのように私が期待します知っています安全です。私は標準からいくつかの確認なしにそれを使用することができますか分からない。共有std::list

+1

[std :: list threading push \ _back、front、pop \ _front]の可能な複製(http://stackoverflow.com/questions/1843567/stdlist-threading-push-back-front-pop-front) – peval27

+0

複製物は古く、物事が変わった。 –

+0

よく、OPはC++標準を指定していません。 – peval27

答えて

1

は、それは彼らが同じ 位置イテレータとそれを呼び出すことはありませんが保証されている場合は、複数のスレッドが同時に挿入 をコールするために安全ですか?

いいえ...(何であれ)安全ではありません。

std::listに挿入すると、反復子の位置にprevious nodenext node、リストのsizeにアクセスする必要があります。

位置が互いに離れていても、std::list::size()一定時間(C++ 11)である必要があります。つまり、挿入するたびにstd::list::size()が更新されます。


編集:共有std::forward_list

は、それらが同じ位置イテレータと それを呼び出すことはありませんが保証されて同時に場合 コールinsert_afterに、複数のスレッドのために、それは安全なのですか?

安全ではありません。推奨されていません。スレッドセーフでSTLコンテナは設計されていませんでした。


とにかく、いくつかの基本的な保証とさせて頂きます、std::forward_listの非常に単純なバージョンとさせて頂きます:insert_afterは、ノードが今、新たに挿入されたノードを指すようにノードがあなたのイテレータによって指さ変更し、新たに挿入されたノード間次のノードを指します。したがって、イテレータが少なくとも2つのノードから離れていて、アロケータがスレッドセーフであれば、そのノードは「安全」になります。

イラスト:あなたが見ることができるように

Initial Forward_list 
A -> B -> C -> D -> E 

Insert K after C 
A -> B -> C x D -> E 
      \/
      K 

Cを読み取って変更されます。 Dが読み込まれ、Kが挿入されてもよい。私は「2つ以上のノードを離れて」と述べた理由として


、離れて1つのノードの場合を取ることができます:それぞれ仮定すると、2つのスレッドがCKを挿入したい、とDM

Initial Forward_list 
A -> B -> C -> D -> E 

Insert K after C 
A -> B -> C x D x E 
      \/\/
      K M 

cppreferenceから:

式の評価は、メモリ位置とに書き込み別の評価で同じメモリ位置が読み込まれたり変更されたりすると、 という式は競合すると言われています。

+0

実際に私のコードで 'std :: list'ではなく' std :: forward_list'を使用しています。簡単にするために 'std :: list'を使って質問を書いていましたが、今変更しました。興味深いことに 'std :: forward_list'にはサイズメソッドがありませんので、あなたの答えは変わりますか? – atb

+0

二重リンクリストでは正しいですが、単一のリンクリストでは、変更が必要な唯一の2つのノードは、位置引数と新しく作成されたノードによって指し示されたノードで、どちらも別のスレッドによって読み取られたり変更されたりしません。 – atb

+0

@atb、私はあなたのコメントを主張するために私の答えを更新しました(イラスト参照)。あなたはまだ少なくとも2つのノードを離れている必要があります。それでも、それは保証ではありません。スレッドセーフリスト付きのロックまたはその他のライブラリを使用する – WhiZTiM

2

さえ別のスレッドは(更なる説明のためにhereを参照)リストにを書き込むとき操作はスレッドセーフで読み;

ロックを解除してリストを読み込んでも、リストが破損することはありません。別のスレッドが読み込み中にリストが変更された場合、どちらかのスレッドが破損する可能性があります、または不正確な結果を生成する)。

ロックが必要です。期間。

+0

あなたは正しいかもしれませんが、私はこの特定の場合になぜなぜ知りたいでしょう。 – atb

+0

それほど単純ではありません。例えば、一つの要素からなるフォワードリストを持っていれば、一つのスレッドからデータを読み込み( 'a = l.begin() - > d;')、別のスレッドに要素を追加する( 'l.insert_after begin()、X);)は競合状態を引き起こすべきではありません。これは、両方のスレッドで共有データがアクセスされていないためです。最初のスレッドは 'begin'と' begin-> d'だけ読み込み、2つ目は 'begin-> next'に書き込みます。 @ShmuelH。 –

+0

AFAIKでは、constバージョンを呼び出さない限り、 'begin'がスレッドセーフであるという保証はありません。 'begin()'は何かできます(それができたら狂ってしまいますが、標準ではそれが許すと思います)。 – NathanOliver

関連する問題