58

私はそれを読みました

「オブジェクトが所有権を共有するコピーであっても、複数のスレッドが異なる shared_ptr オブジェクトを同時に読み書きできます。」( MSDN: 標準 C++ ライブラリのスレッド セーフ)

それは shared_ptr オブジェクトの変更が安全であることを意味しますか?
たとえば、安全と見なされる次のコードは次のとおりです。

shared_ptr<myClass> global = make_shared<myClass>();
...

//In thread 1
shared_ptr<myClass> private = global;
...

//In thread 2
global = make_shared<myClass>();
...

その場合、スレッド 1privateが元の値globalまたはスレッド 2 が割り当てた新しい値を持つことを確信できますか?

==編集==
私の動機を説明するだけです。構成を保持するための共有ポインターが必要であり、要求を処理するためのスレッド プールがあります。グローバル構成も同様です
。リクエストの処理を開始すると、現在の構成が取得されます。構成を更新しています。(将来の要求にのみ適用) global
thread 1
thread 2

それが機能する場合は、リクエスト処理の途中で構成を壊すことなく、そのように構成を更新できます。

4

7 に答える 7

108

あなたが読んでいることは、あなたがそれが意味すると思っていることを意味していません. まず、shared_ptr自体の msdn ページを試してください。

「備考」セクションまで下にスクロールすると、問題の要点が表示されます。基本的に、aは「実」オブジェクトを実際に指しているオブジェクトshared_ptr<>の数を追跡する方法である「制御ブロック」を指します。shared_ptr<>したがって、これを行うと:

shared_ptr<int> ptr1 = make_shared<int>();

ここで を介してメモリを割り当てる呼び出しは 1 つしかありmake_sharedませんが、同じように扱うべきではない 2 つの「論理」ブロックがあります。1 つはint実際の値を格納するブロックで、もう1 つはshared_ptr<>それを機能させるすべての「魔法」を格納する制御ブロックです。

スレッドセーフであるのは制御ブロック自体だけです。

強調するために、それを独自の行に入れました。の内容はスレッドセーフではなく、同じインスタンスshared_ptrへの書き込みでもありません。shared_ptrここに私が何を意味するかを示すものがあります:

// In main()
shared_ptr<myClass> global_instance = make_shared<myClass>();
// (launch all other threads AFTER global_instance is fully constructed)

//In thread 1
shared_ptr<myClass> local_instance = global_instance;

これは問題ありません。実際、すべてのスレッドで必要なだけこれを行うことができます。そして、local_instance(スコープ外に出ることによって) が破棄されると、スレッドセーフでもあります。誰かがアクセスできてもglobal_instance、違いはありません。msdn から取得したスニペットは、基本的に「制御ブロックへのアクセスはスレッドセーフ」であることを意味するためshared_ptr<>、必要に応じて異なるスレッドで他のインスタンスを作成および破棄できます。

//In thread 1
local_instance = make_shared<myClass>();

これで問題ありません。オブジェクトに影響しますglobal_instance、間接的にのみ影響します。それが指す制御ブロックはデクリメントされますが、スレッドセーフな方法で行われます。 local_instance同じオブジェクト (または制御ブロック) を指すことはなくなりますglobal_instance

//In thread 2
global_instance = make_shared<myClass>();

他のスレッドからアクセスされた場合、これはほぼ確実に問題ありませんglobal_instance(あなたが行っていると言っています)。これを行う場合はglobal_instance、読み取りだけでなく、どこにでも書き込むため、ロックが必要です。したがって、複数のスレッドからオブジェクトへの書き込みは、ロックによって保護されていない限り、悪いことです。global_instanceしたがって、オブジェクトから新しいオブジェクトを割り当てることでオブジェクトから読み取ることはできshared_ptr<>ますが、書き込むことはできません。

// In thread 3
*global_instance = 3;
int a = *global_instance;

// In thread 4
*global_instance = 7;

の値aは未定義です。7 かもしれないし、3 かもしれないし、それ以外かもしれません。インスタンスのスレッドセーフは、互いに初期化されたインスタンスのshared_ptr<>管理にのみ適用され、それらが指しているものには適用されません。shared_ptr<>

私が言いたいことを強調するために、これを見てください:

shared_ptr<int> global_instance = make_shared<int>(0);

void thread_fcn();

int main(int argc, char** argv)
{
    thread thread1(thread_fcn);
    thread thread2(thread_fcn);
    ...
    thread thread10(thread_fcn);

    chrono::milliseconds duration(10000);
    this_thread::sleep_for(duration);

    return;
}

void thread_fcn()
{
    // This is thread-safe and will work fine, though it's useless.  Many
    // short-lived pointers will be created and destroyed.
    for(int i = 0; i < 10000; i++)
    {
        shared_ptr<int> temp = global_instance;
    }

    // This is not thread-safe.  While all the threads are the same, the
    // "final" value of this is almost certainly NOT going to be
    // number_of_threads*10000 = 100,000.  It'll be something else.
    for(int i = 0; i < 10000; i++)
    {
        *global_instance = *global_instance + 1;
    }
}

Aは、複数のスレッドがオブジェクトに正しくアクセスできることを保証するメカニズムではなく、shared_ptr<>複数のオブジェクト所有者がオブジェクトが破棄されることを保証するメカニズムです。複数のスレッドで安全に使用するには、別の同期メカニズムが必要です ( std::mutexなど)。

IMO について考える最良の方法はshared_ptr<>、同じメモリを指す複数のコピーがそれ自体の同期の問題を抱えていないことを確認することですが、指しているオブジェクトに対しては何もしません。そのように扱ってください。

于 2013-01-23T17:05:54.887 に答える
31

Kevin の記述に加えて、C++14 仕様では、shared_ptr オブジェクト自体へのアトミック アクセスが追加でサポートされています。

20.8.2.6shared_ptrアトミック アクセス [util.smartptr.shared.atomic]

複数のスレッドからのオブジェクトへの同時アクセスshared_ptrは、アクセスがこのセクションの関数を介して排他的に行われ、インスタンスが最初の引数として渡される場合、データ競合を引き起こしません。

したがって、次のようにします。

//In thread 1
shared_ptr<myClass> private = atomic_load(&global);
...

//In thread 2
atomic_store(&global, make_shared<myClass>());
...

スレッドセーフになります。

于 2015-03-14T03:45:11.900 に答える
4

shared_ptrこれは、有効なと有効な参照カウントを持つことを意味します。

同じ変数の読み取り/割り当てを試みている 2 つのスレッド間の競合状態について説明しています。

これは一般的に未定義の動作であるため(個々のプログラムのコンテキストとタイミングでのみ意味があります)、shared_ptrそれを処理しません。

于 2013-01-23T15:12:24.963 に答える
1

この質問に対するこれまでの回答は、説明されているシナリオに関して誤解を招くものだと思います。質問で説明されている非常によく似たシナリオがあります。他のすべてのスレッドには、現在の構成への読み取り専用アクセスのみが必要です。これは、次の方法で実現されます。

// In thread n
shared_ptr<MyConfig> sp_local = sp_global;

これらのスレッドはいずれも、MyConfigオブジェクトのコンテンツを変更しません。参照カウントsp_globalは、上記の行を実行するたびにインクリメントされます。

スレッド 1sp_globalは、構成の別のインスタンスに定期的にリセットします。

// In thread 1
shared_ptr<MyConfig> sp_global = make_shared<MyConfig>(new MyConfig);

これも安全なはずです。の参照カウントsp_globalを 1 に戻し、sp_globalすべての新しいローカル コピーと同様に、最新の構成を指します。したがって、ここで何も欠落していなければ、これはすべて完全にスレッドセーフであるはずです。

#include <iostream>
#include <memory>

using namespace std;

shared_ptr<int> sp1(new int(10));

int main()
{
    cout<<"Hello World! \n";

    cout << "sp1 use count: " << sp1.use_count() << ", sp1: " << *sp1 << "\n";
    cout << "---------\n";

    shared_ptr<int> sp2 = sp1;
    shared_ptr<int>* psp3 = new shared_ptr<int>;
    *psp3 = sp1;
    cout << "sp1 use count: " << sp1.use_count() << ", sp1: " << *sp1 << "\n";
    cout << "sp2 use count: " << sp2.use_count() << ", sp2: " << *sp2 << "\n";
    cout << "sp3 use count: " << psp3->use_count() << ", sp3: " << *(*psp3) << "\n";
    cout << "---------\n";

    sp1.reset(new int(20));

    cout << "sp1 use count: " << sp1.use_count() << ", sp1: " << *sp1 << "\n";
    cout << "sp2 use count: " << sp2.use_count() << ", sp2: " << *sp2 << "\n";
    cout << "sp3 use count: " << psp3->use_count() << ", sp3: " << *(*psp3) << "\n";
    cout << "---------\n";

    delete psp3;
    cout << "sp1 use count: " << sp1.use_count() << ", sp1: " << *sp1 << "\n";
    cout << "sp2 use count: " << sp2.use_count() << ", sp2: " << *sp2 << "\n";
    cout << "---------\n";

    sp1 = nullptr;

    cout << "sp1 use count: " << sp1.use_count() << "\n";
    cout << "sp2 use count: " << sp2.use_count() << ", sp2: " << *sp2 << "\n";

    return 0;
}

そして出力

Hello World!
sp1 use count: 1, sp1: 10
---------
sp1 use count: 3, sp1: 10
sp2 use count: 3, sp2: 10
sp3 use count: 3, sp3: 10
---------
sp1 use count: 1, sp1: 20
sp2 use count: 2, sp2: 10
sp3 use count: 2, sp3: 10
---------
sp1 use count: 1, sp1: 20
sp2 use count: 1, sp2: 10
---------
sp1 use count: 0
sp2 use count: 1, sp2: 10
于 2019-05-25T23:12:07.230 に答える