6

マルチスレッド アプリケーションのどこからでもアクセスできるいくつかの設定クラスを作成しています。私はこれらの設定を非常に頻繁に読み取ります (読み取りアクセスは高速である必要があります) が、頻繁に書き込まれるわけではありません。

プリミティブ データ型の場合、必要なものを提供しているように見えるboost::atomicので、次のようなものを思いつきました。

class UInt16Setting
{
    private:
        boost::atomic<uint16_t> _Value;
    public:
        uint16_t getValue() const { return _Value.load(boost::memory_order_relaxed); }
        void setValue(uint16_t value) { _Value.store(value, boost::memory_order_relaxed); }
};

質問 1:メモリの順序がわかりません。私のアプリケーションでは、メモリの順序はあまり気にしないと思います (そうですか?)。getValue()が常に破損していない値 (古い値または新しい値) を返すことを確認したいだけです。私のメモリ順序設定は正しいですか?

質問 2:このアプローチはboost::atomic、この種の同期に推奨されますか? または、より優れた読み取りパフォーマンスを提供する他の構成要素はありますか?

std::stringまた、アプリケーションでは、s のリストなど、より複雑な設定タイプが必要になりますboost::asio::ip::tcp::endpoint。これらの設定値はすべて不変であると考えています。したがって、 を使用して値を設定するsetValue()と、値自体 (std::stringまたはエンドポイントのリスト自体) は変更されなくなります。繰り返しますが、古い値または新しい値のいずれかを取得することを確認したいだけですが、破損した状態ではありません。

質問 3:このアプローチは で機能しboost::atomic<std::string>ますか? そうでない場合、代替手段は何ですか?

質問 4:エンドポイントのリストなど、より複雑な設定タイプはどうですか? のようなものをお勧めしますboost::atomic<boost::shared_ptr<std::vector<boost::asio::ip::tcp::endpoint>>>か? そうでない場合、何が良いでしょうか?

4

3 に答える 3

2

質問1の場合、答えは「依存しますが、おそらくそうではない」です。単一の値が文字化けしないことのみを本当に気にする場合は、はい、これで問題ありません。また、メモリの順序も気にしません。
ただし、通常、これは誤った前提です。

質問23、および4については、はい、これは機能しますが、string(内部的に、アクセスごとに、知らないうちに) などの複雑なオブジェクトに対してロックを使用する可能性があります。通常は、1 つまたは 2 つのポインターのサイズとほぼ同じサイズのかなり小さなオブジェクトのみが、ロックフリーの方法でアトミックにアクセス/変更できます。これもプラットフォームによって異なります。

アトミックに 1 つまたは 2 つの値を正常に更新できるかどうかは大きな違いです。タスクが配列内で何らかの処理を行う場所の左右の境界を区切るleftとの値があるとします。rightそれらがそれぞれ 50 と 100 であると仮定し、それぞれアトミックに 101 と 150 に変更します。したがって、もう一方のスレッドは 50 から 101 への変更を取得して計算を開始し、101 > 100 であることを確認して終了し、結果をファイルに書き込みます。その後、出力ファイルの名前をアトミックに変更します。
すべてがアトミック (したがって、通常よりも高価) でしたが、どれも役に立ちませんでした。結果はまだ間違っており、間違ったファイルにも書き込まれています。
これは特定のケースでは問題にならないかもしれませんが、通常は問題になります (また、要件は将来変更される可能性があります)。通常、変更の完全なセットがアトミックであることを本当に望んでいます。

そうは言っても、このような多くのまたは複雑な (または、多くと複雑な) 更新を行う場合は、最初に構成全体に対して 1 つの大きな (リーダーライター) ロックを使用することをお勧めします。 20 または 30 のロックを取得して解放したり、50 または 100 のアトミック操作を実行したりするよりも効率的です。ただし、いずれの場合も、ロックはパフォーマンスに深刻な影響を与えることに注意してください。

上記のコメントで指摘したように、構成を変更する 1 つのスレッドから構成のディープ コピーを作成し、コンシューマーが通常のタスクとして使用する参照 (共有ポインター) の更新をスケジュールすることをお勧めします。このコピー、変更、公開のアプローチは、MVCC データベースの仕組みにも少し似ています (これらにも、ロックによってパフォーマンスが低下するという問題があります)。

コピーを変更すると、リーダーのみが共有状態にアクセスすることがアサートされるため、リーダーにも単一のライターにも同期は必要ありません。読み取りと書き込みは高速です。構成セットの交換は、セットが完全で一貫した状態にあることが保証され、スレッドが他のことをしないことが保証されている、明確に定義された時点でのみ発生するため、いかなる種類の醜い驚きも起こりません。

典型的なタスク駆動型アプリケーションは、次のようになります (C++ に似た擬似コードで)。

// consumer/worker thread(s)
for(;;)
{
    task = queue.pop();

    switch(task.code)
    {
        case EXIT:
            return;

        case SET_CONFIG:
            my_conf = task.data;
            break;

        default:
            task.func(task.data, &my_conf); // can read without sync
    }
}


// thread that interacts with user (also producer)
for(;;)
{
    input = get_input();

    if(input.action == QUIT)
    {
        queue.push(task(EXIT, 0, 0));
        for(auto threads : thread)
            thread.join();
        return 0;
    }
    else if(input.action == CHANGE_SETTINGS)
    {
        new_config = new config(config); // copy, readonly operation, no sync
        // assume we have operator[] overloaded
        new_config[...] = ...;           // I own this exclusively, no sync

        task t(SET_CONFIG, 0, shared_ptr<...>(input.data));
        queue.push(t);
    }
    else if(input.action() == ADD_TASK)
    {
        task t(RUN, input.func, input.data);
        queue.push(t);
    }
    ...
}
于 2013-11-01T16:09:20.663 に答える
2

Q1、アトミックを読み取った後、共有された非アトミック変数を読み取ろうとしない場合は修正してください。メモリ バリアは、アトミック操作の間に発生する可能性のある非アトミック変数へのアクセスのみを同期します。

Q2 わからない(ただし下記参照)

Q3 動作するはずです (コンパイルされた場合)。でも、

 atomic<string> 

おそらくロックフリーではない

Q4 動作するはずですが、やはり、実装がロックフリーではない可能性があります (ロックフリーの shared_ptr の実装は困難であり、特許が採掘されている分野です)。

したがって、構成に1マシンワードを超えるサイズのデータ​​が含まれている場合(通常、CPUネイティブアトミックが機能する場合)、おそらくリーダーライターロック(デイモンがコメントで示唆しているように)はよりシンプルでさらに効果的です。

[編集]しかし、

atomic<shared_ptr<TheWholeStructContainigAll> > 

ロックフリーでなくても意味があるかもしれません。このアプローチは、複数の一貫した値を必要とするリーダーの衝突確率を最小限に抑えますが、ライターは何かを変更するたびに「パラメーターシート」全体の新しいコピーを作成する必要があります。

于 2013-11-01T14:12:38.870 に答える
2

ポインターよりも重要なものについては、mutex を使用します。tbb (オープンソース) ライブラリは、複数の同時リーダーを許可するリーダー/ライター ミュートの概念をサポートしています。ドキュメントを参照してください。

于 2013-11-01T22:44:22.293 に答える