std :: shared_ptr线程安全

Roe*_*rel 43 c++ std shared-ptr

我读过这个

"多个线程可以同时读写不同的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>();
...
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我可以确定线程1 private将具有global线程2分配的原始值或新值,但无论哪种方式,它将具有对myClass的有效shared_ptr?

==编辑==
只是为了解释我的动机.我想有一个共享指针来保存我的配置,我有一个线程池来处理请求.全局配置也是
如此global.
thread 1在开始处理请求时采用当前配置.
thread 2正在更新配置.(仅适用于未来的请求)

如果它工作,我可以更新配置,而不会在请求处理过程中中断它.

Kev*_*son 87

你所读到的并不意味着你认为它意味着什么.首先,尝试使用shared_ptr本身的msdn页面.

向下滚动到"备注"部分,您将了解问题的主要内容.基本上,shared_ptr<>指向"控制块",它是如何跟踪shared_ptr<>实际指向"真实"对象的对象的数量.所以当你这样做时:

shared_ptr<int> ptr1 = make_shared<int>();
Run Code Online (Sandbox Code Playgroud)

虽然这里只有1个调用来分配内存make_shared,但是有两个"逻辑"块你不应该对它们进行相同的处理.一个是int存储实际值的,另一个是控制块,它存储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;
Run Code Online (Sandbox Code Playgroud)

这很好,事实上你可以在所有线程中尽可能多地执行此操作.然后当local_instance被破坏时(超出范围),它也是线程安全的.有人可以访问global_instance,它不会有所作为.从msdn中提取的片段基本上意味着"访问控制块是线程安全的",因此shared_ptr<>可以根据需要在不同的线程上创建和销毁其他实例.

//In thread 1
local_instance = make_shared<myClass>();
Run Code Online (Sandbox Code Playgroud)

这可以.它影响global_instance对象,但只是间接影响.它指向的控制块将递减,但以线程安全的方式完成. local_instance将不再指向同一个对象(或控制块)global_instance.

//In thread 2
global_instance = make_shared<myClass>();
Run Code Online (Sandbox Code Playgroud)

如果global_instance从任何其他线程(您说你正在做)访问,这几乎肯定是不好的.如果你这样做,它需要锁定,因为你写的是global_instance生活的任何地方,而不仅仅是从中读取.因此,从多个线程写入对象是不好的,除非你通过锁保护它.因此,您可以global_instance通过从对象中分配新shared_ptr<>对象来读取对象,但您无法写入该对象.

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

// In thread 4
*global_instance = 7;
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

A shared_ptr<>是一种确保多个对象所有者确保对象被破坏的机制,而不是确保多个线程可以正确访问对象的机制.您仍然需要一个单独的同步机制来在多个线程中安全地使用它(比如std :: mutex).

考虑它的最佳方式IMO是shared_ptr<>确保指向同一内存的多个副本本身没有同步问题,但不会对指向的对象执行任何操作.像那样对待它.

  • 你在第一行中说的是假的.make_shared存在的非常好的副作用是能够将int和ref计数器类嵌套到同一个内存块中.(避免碎片和限制缓存未命中,也避免调用2`new`的速度慢).使用`make_shared`只有一个`malloc`和2个放置新闻. (6认同)
  • v.oddou是对的,我只有一个内存分配.然后,Zero(和Kevin)是正确的,从逻辑上讲,有两个内存区域,其中只有一个是线程安全的.不过,我认为重要的是要指出make_shared只执行一次内存分配. (5认同)
  • 我已经说过,虽然可能不是你想要的方式:"它一次完成,但它是两个"逻辑"块." (3认同)
  • 你需要进一步阅读:然后真正发生的事情是分配两个不同的内存部分.它一次完成,但它是两个"逻辑"块.将它视为两个逻辑块对于理解什么是线程安全是非常重要. (2认同)
  • 忘记命名,这是一个很好的答案! (2认同)
  • @KevinAnderson我可以用'主,在thread1`是不是不安全的,而其他线程写入它从全球看,难道是断写来读错了,但在你的第一个"这是好的"片段/读那里?不确定构造是原子的 (2认同)
  • @OlegBogdanov我想你在这里有一个有效的解释.我的例子有点"不尽如人意",因为我暗示`main`动作发生了,然后你启动了线程,这当然可能不对.我会编辑这个以使它更清楚,这就是我的意思.`global_instance`应该在另一个线程中使用之前完全构造. (2认同)

Chr*_*odd 24

为了补充Kevin写的内容,C++ 14规范还支持对shared_ptr对象本身的原子访问:

20.8.2.6 shared_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>());
...
Run Code Online (Sandbox Code Playgroud)

它将是线程安全的.

  • 它是C++ 11标准第20.7.2.5节的一部分. (5认同)

Yoc*_*mer 5

这意味着您将拥有有效的shared_ptr和 有效的引用计数。

您正在描述试图读取/分配给同一变量的两个线程之间的竞争条件。

因为这通常是未定义的行为(它仅在单个程序的上下文和时间中有意义),因此shared_ptr无法处理该问题。