1

私は現在、C++ STL コンテナーを使用してスレッドセーフの問題に頭を悩ませようとしています。最近、メンバー変数として std::mutex を使用してスレッド セーフな std::vector を実装しようとしましたが、ロックをロックすることでメンバー関数をスレッド セーフにすることはできましたが、lib 関数を作成することはできませんでした。 std::sort スレッドセーフのように、begin()/end() イテレータのみを取得するためです。これは、一般的な STL のコンテナとアルゴリズムの間の基本的な分割の結果です。

では、ロックを使用できない場合は、ソフトウェア トランザクショナル メモリ (STM) はどうでしょうか。

だから今、私はこれにこだわっています:

#include <atomic>
#include <cstdlib>
#include <iostream>
#include <thread>
#include <vector>

#define LIMIT 10

std::atomic<bool> start{false};
std::vector<char> vec;

void thread(char c)
{
    while (!start)
        std::this_thread::yield();

    for (int i = 0; i < LIMIT; ++i) {
        __transaction_atomic {
        vec.push_back(c);
        }
    }
}

int main()
{
    std::thread t1(thread, '*');
    std::thread t2(thread, '@');

    start.store(true);

    t1.join();
    t2.join();

    for (auto i = vec.begin(); i != vec.end(); ++i)
        std::cout << *i;

    std::cout << std::endl;

    return EXIT_SUCCESS;
}

私がコンパイルするもの:

g++ -std=c++11 -fgnu-tm -Wall

g++ 4.8.2 を使用すると、次のエラーが表示されます。

error: unsafe function call to push_back within atomic transaction

push_back や sort などは transaction_safe と宣言されていないため、これはちょっとわかりますが、次の質問が残ります。

a) どうすればそのエラーを修正できますか?

b) そのエラーを修正できない場合、これらのトランザクション ブロックは通常何に使用されますか?

c) ロックフリーのスレッドセーフなベクトルをどのように実装しますか?!

前もって感謝します!

編集: これまでの回答に感謝しますが、私のかゆみを実際に掻くことはありません. 例を挙げましょう。グローバル ベクトルがあり、このベクトルへのアクセスが複数のスレッド間で共有されるとします。すべてのスレッドはソートされた挿入を試みるため、乱数を生成し、この番号をソートされた方法でベクトルに挿入しようとするため、ベクトルは常にソートされたままになります (c の複製を含む)。ソートされた挿入を行うには、 std::lower_bound を使用して挿入する「インデックス」を見つけ、次に vector.insert() を使用して挿入を行います。

メンバーとして std::mutex を含む std::vector のラッパーを作成すると、ラッパー関数を作成できます。たとえば、std::lock_guard を使用してミューテックスをロックし、実際の std::vector を実行する insert などです。 insert() 呼び出し。しかし、 std::lower_bound はメンバーミューテックスについて気にしません。これは機能であり、バグではありません。

これにより、誰かが lower_bound を実行している間に他のスレッドがベクトルを変更できるため、私のスレッドはかなり厄介なままになります。

私が考えることができる唯一の修正: ラッパーを忘れて、代わりにベクトルのグローバル ミューテックスを持っています。誰かがこのベクトルに対して/これを使って何かをしたいときはいつでも、そのロックが必要です。

それが問題です。このグローバルミューテックスを使用するための代替手段は何ですか? ソフトウェアトランザクションメモリが頭に浮かんだのはそれです。

それでは、STL コンテナーで STM を使用する方法を教えてください。(および a)、b)、c) 上から)。

4

3 に答える 3

2

STL コンテナーを 100% スレッド セーフにする唯一の方法は、それを独自のオブジェクトにラップし (実際のコンテナーをプライベートに保ちます)、オブジェクトで適切なロック (ミューテックスなど) を使用してマルチスレッドを防止することだと思います。 STL コンテナにアクセスします。

これは、すべてのコンテナー操作の呼び出し元でミューテックスをロックすることと同等です。

コンテナーを真にスレッド セーフにするためには、コンテナー コードをいじる必要がありますが、これに対する規定はありません。

編集:もう1つの注意 - ラッパーオブジェクトに与えるインターフェースに注意してください。呼び出し元がラッパーのロックを回避できるようになるため、保存されたオブジェクトへの参照を渡すことはあまりうまくいきません。そのため、ベクトルのインターフェイスをミューテックスで複製して、機能することを期待することはできません。

于 2013-12-18T20:38:44.713 に答える
0

ミューテックスを使用できない理由がわかりません。ベクトルにアクセスするたびにミューテックスをロックすると、どのような操作を行っていても、一度に 1 つのスレッドだけがそれを使用していることが確実になります。安全なベクターのニーズに応じて改善の余地は確かにありますが、ミューテックスは完全に実行可能です。

ミューテックスをロックする -> std::sort または必要なものを呼び出す -> ミューテックスのロックを解除する

反対に、クラスで std::sort を使用したい場合は、コンテナーのイテレーターを介してスレッドセーフなアクセスと読み取りメソッドを提供することが重要です。ベクトルをソートするために sort を使用する必要があります。これは、コンテナーのフレンドでも、そのようなものでもないためです。

于 2013-12-18T20:20:44.373 に答える
0

単純なミューテックスを使用して、クラスをスレッド セーフにすることができます。別の回答で述べたように、ミューテックスを使用して使用前にベクターをロックし、使用後にロックを解除する必要があります。

あぶない! STL 関数はすべて例外をスローできます。単純なミューテックスを使用すると、ミューテックスが解放されないため、関数がスローされた場合に問題が発生します。この問題を回避するには、デストラクタでミューテックスを解放するクラスでミューテックスをラップします。これは、学ぶのに良いプログラミングの実践です: http://c2.com/cgi/wiki?ResourceAcquisitionIsInitialization

于 2013-12-18T20:49:14.813 に答える