C++ std::shared_ptr 线程安全
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14482830/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
std::shared_ptr thread safety
提问by Roee Gavirel
I've read that
我读过那个
"Multiple threads can simultaneously read and write different shared_ptr objects, even when the objects are copies that share ownership." (MSDN: Thread Safety in the Standard C++ Library)
“多个线程可以同时读取和写入不同的 shared_ptr 对象,即使这些对象是共享所有权的副本。” (MSDN:标准 C++ 库中的线程安全)
Does that mean that changing shared_ptr object is safe ?
For an instance, is the next code considered safe:
这是否意味着更改 shared_ptr 对象是安全的?
例如,下一个代码是否被认为是安全的:
shared_ptr<myClass> global = make_shared<myClass>();
...
//In thread 1
shared_ptr<myClass> private = global;
...
//In thread 2
global = make_shared<myClass>();
...
Can I be sure in that case that thread 1 private
will have the original value of global
or the new value which thread 2 assigned but either way it will have a valid shared_ptr to myClass?
在这种情况下,我可以确定线程 1private
将具有global
线程 2 分配的原始值或新值,但无论哪种方式,它都会对 myClass 具有有效的 shared_ptr 吗?
==EDIT==
Just to explain my motivation. I want to have a shared pointer to hold my configuration and I have a thread pool to handle requests.
so global
is the global configuration.thread 1
is taking the current configuration as it start to handle a request.thread 2
is updating the configuration. (only apply to future requests)
==编辑==
只是为了解释我的动机。我想要一个共享指针来保存我的配置,并且我有一个线程池来处理请求。全局配置也是
如此global
。thread 1
在开始处理请求时采用当前配置。thread 2
正在更新配置。(仅适用于未来的请求)
If it's work, I can update the configuration that way without breaking it in the middle of a request handling.
如果可行,我可以以这种方式更新配置,而不会在请求处理过程中破坏它。
回答by Kevin Anderson
What you're reading isn't meaning what you think it means. First of all, try the msdn page for shared_ptritself.
你正在阅读的内容并不意味着你认为它意味着什么。首先,尝试shared_ptr本身的 msdn 页面。
Scroll down into the "Remarks" section and you'll get to the meat of the issue. Basically, a shared_ptr<>
points to a "control block" which is how it keeps track of how many shared_ptr<>
objects are actually pointing to the "Real" object. So when you do this:
向下滚动到“备注”部分,您将了解问题的实质。基本上,ashared_ptr<>
指向一个“控制块”,这就是它如何跟踪shared_ptr<>
实际指向“真实”对象的对象数量。所以当你这样做时:
shared_ptr<int> ptr1 = make_shared<int>();
While there is only 1 call to allocate memory here via make_shared
, there are two "logical" blocks that you should not treat the same. One is the int
which stores the actual value, and the other is the control block, which stores all the shared_ptr<>
"magic" that makes it work.
虽然这里只有 1 次调用通过 分配内存make_shared
,但有两个“逻辑”块您不应该对待相同的块。一个是int
存储实际值的 ,另一个是控制块,它存储shared_ptr<>
使其工作的所有“魔法”。
It is only the control block itself which is thread-safe.
只有控制块本身是线程安全的。
I put that on its own line for emphasis. The contentsof the shared_ptr
are not thread-safe, nor is writing to the same shared_ptr
instance. Here's something to demonstrate what I mean:
为了强调,我把它放在自己的行上。该内容的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;
This is fine, in fact you can do this in all threads as much as you want. And then when local_instance
is destructed (by going out of scope), it is also thread-safe. Somebody can be accessing global_instance
and it won't make a difference. The snippet you pulled from msdn basically means "access to the control block is thread-safe" so other shared_ptr<>
instances can be created and destroyed on different threads as much as necessary.
这很好,事实上你可以在所有线程中尽可能多地这样做。然后当local_instance
被破坏时(通过超出范围),它也是线程安全的。有人可以访问global_instance
,这不会有什么不同。您从 msdn 中提取的代码段基本上意味着“对控制块的访问是线程安全的”,因此shared_ptr<>
可以根据需要在不同的线程上创建和销毁其他实例。
//In thread 1
local_instance = make_shared<myClass>();
This is fine. It willaffect the global_instance
object, but only indirectly. The control block it points to will be decremented, but done in a thread-safe way. local_instance
will no longer point to the same object (or control block) as global_instance
does.
这可以。它会影响global_instance
对象,但只是间接的。它指向的控制块将被递减,但以线程安全的方式完成。 local_instance
将不再指向同一个对象(或控制块)global_instance
。
//In thread 2
global_instance = make_shared<myClass>();
This is almost certainly not fine if global_instance
is accessed from any other threads (which you say you're doing). It needs a lock if you're doing this because you're writing to wherever global_instance
lives, not just reading from it. So writing to an object from multiple threads is bad unless it's you have guarded it through a lock. So you can read from global_instance
the object by assigning new shared_ptr<>
objects from it but you can't write to it.
如果global_instance
从任何其他线程(你说你正在做)访问,这几乎肯定是不好的。如果你这样做,它需要一个锁,因为你要写到任何地方global_instance
,而不仅仅是从中读取。所以从多个线程写入一个对象是不好的,除非你通过锁保护它。因此,您可以global_instance
通过从中分配新shared_ptr<>
对象来读取该对象,但不能写入该对象。
// In thread 3
*global_instance = 3;
int a = *global_instance;
// In thread 4
*global_instance = 7;
The value of a
is undefined. It might be 7, or it might be 3, or it might be anything else as well. The thread-safety of the shared_ptr<>
instances only applies to managing shared_ptr<>
instances which were initialized from each other, not what they're pointing to.
的值a
未定义。它可能是 7,也可能是 3,也可能是其他任何东西。shared_ptr<>
实例的线程安全仅适用于管理shared_ptr<>
彼此初始化的实例,而不适用于它们所指向的实例。
To emphasize what I mean, look at this:
为了强调我的意思,看看这个:
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<>
is a mechanism to ensure that multiple object ownersensure an object is destructed, not a mechanism to ensure multiple threadscan access an object correctly. You still need a separate synchronization mechanism to use it safely in multiple threads (like std::mutex).
Ashared_ptr<>
是一种确保多个对象所有者确保一个对象被销毁的机制,而不是一种确保多个线程可以正确访问一个对象的机制。您仍然需要一个单独的同步机制来在多个线程中安全地使用它(如std::mutex)。
The best way to think about it IMO is that shared_ptr<>
makes sure that multiple copies pointing to the same memory don't have synchronization issues for itself, but doesn't do anything for the object pointed to. Treat it like that.
考虑 IMO 的最佳方式是shared_ptr<>
确保指向同一内存的多个副本本身没有同步问题,但不会对指向的对象执行任何操作。那样对待它。
回答by Chris Dodd
To add to what Kevin wrote, the C++14 spec has additional support for atomic access to shared_ptr objects themselves:
为了补充 Kevin 所写的内容,C++14 规范对 shared_ptr 对象本身的原子访问提供了额外的支持:
20.8.2.6
shared_ptr
atomic access [util.smartptr.shared.atomic]Concurrent access to a
shared_ptr
object from multiple threads does not introduce a data race if the access is done exclusively via the functions in this section and the instance is passed as their first argument.
20.8.2.6
shared_ptr
原子访问 [util.smartptr.shared.atomic]
shared_ptr
如果访问完全通过本节中的函数完成并且实例作为它们的第一个参数传递,那么从多个线程并发访问对象不会引入数据竞争。
So if you do:
所以如果你这样做:
//In thread 1
shared_ptr<myClass> private = atomic_load(&global);
...
//In thread 2
atomic_store(&global, make_shared<myClass>());
...
it will be thread safe.
它将是线程安全的。
回答by Yochai Timmer
It means you will have a valid shared_ptr
, and a valid reference counting.
这意味着您将拥有一个有效的shared_ptr
和 有效的引用计数。
You're describing a race condition between 2 threads that are trying to read/assign to the same variable.
您正在描述试图读取/分配给同一变量的 2 个线程之间的竞争条件。
Because this is undefined behavior in general (it only makes sense in the context and timing of the individual program) shared_ptr
doesn't handle that.
因为这通常是未定义的行为(它仅在单个程序的上下文和时间中有意义)shared_ptr
无法处理。
回答by Leon
Read operations are not subject to data races among themselves, hence it is safe to share the same instance of the shared_ptr between threads as long as all threads use const methods only(this includes creating copies of it). As soon as one thread uses non-const method (as in "point it to another object") such use is no longer thread safe.
读取操作不受它们之间的数据竞争的影响,因此只要所有线程仅使用const 方法(这包括创建它的副本),就可以安全地在线程之间共享 shared_ptr 的相同实例。一旦一个线程使用非常量方法(如“将其指向另一个对象”),这种使用就不再是线程安全的。
The OP example is not thread safe and would require the use of atomic load in thread 1. and atomic store in thread 2 (section 2.7.2.5 in C++11) to make it thread safe.
OP 示例不是线程安全的,需要在线程 1. 中使用原子加载并在线程 2 中使用原子存储(C++11 中的第 2.7.2.5 节)以使其线程安全。
The key word in MSDN text is indeed different shared_ptr objects, as already stated in previous answers.
MSDN 文本中的关键词确实是不同的 shared_ptr objects,如之前的答案中所述。
回答by hagh
I think the so far answers to this question are misleading with regard to described scenario. I have a very similar scenario described in the question. All other threads have (need) just a read-only access to current configuration which is achieved through:
我认为到目前为止对这个问题的答案在描述的场景方面具有误导性。我在问题中描述了一个非常相似的场景。所有其他线程(需要)只是对当前配置的只读访问,这是通过以下方式实现的:
// In thread n
shared_ptr<MyConfig> sp_local = sp_global;
None of these threads are going to modify the content of the MyConfig
object. Ref count for sp_global
gets incremented for each execution of the line above.
这些线程都不会修改MyConfig
对象的内容。sp_global
每次执行上述行时,引用计数都会增加。
Thread 1, periodically resets the sp_global
to some another instance of the configuration:
线程 1,定期将 重置sp_global
为配置的另一个实例:
// In thread 1
shared_ptr<MyConfig> sp_global = make_shared<MyConfig>(new MyConfig);
This also should be safe. It sets the ref count of sp_global
back to 1, and the sp_global
now points to the latest configuration, as goes with all the new local copies. So, if I am not missing anything here, this should all be totally thread safe.
这也应该是安全的。它将 ref 计数设置sp_global
为 1,并且sp_global
now 指向最新配置,与所有新的本地副本一样。所以,如果我在这里没有遗漏任何东西,这应该都是完全线程安全的。
#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;
}
and the output
和输出
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
回答by Lujun Weng
here is my understanding of thread safety of shared_ptr. IMO, there are three aspects when it comes to thread safety of shared_ptr.
这是我对 shared_ptr 线程安全的理解。IMO,就 shared_ptr 的线程安全而言,有三个方面。
The first one is shared_ptr itself. I would say shared_ptr itself is not thread safe, which means there is a data race when we try to access oneshared_ptr object in multiple threads and one of the accesses is writing. For example, we have a data race in the following situation:
第一个是 shared_ptr 本身。我会说 shared_ptr 本身不是线程安全的,这意味着当我们尝试在多个线程中访问一个shared_ptr 对象并且其中一个访问是写入时会发生数据竞争。例如,我们在以下情况下进行数据竞争:
# Main Thread
shared_ptr<string> global_ptr = make_shared<string>();
string str = *global_ptr;
# Thread 1
global_ptr.reset();
The second aspect is the internal structure of shared_ptr. I would say it is thread safe. The result is there is no data race when accessing multipleshared_ptr objects and the objects point to the same managed object. For example, we don't have data race in the following situation:
第二个方面是shared_ptr的内部结构。我会说它是线程安全的。结果是在访问多个shared_ptr 对象并且这些对象指向同一个托管对象时没有数据竞争。例如,在以下情况下我们没有数据竞争:
# Main Thread
shared_ptr<string> global_ptr = make_shared<string>();
string str = *global_ptr;
# Thread 1
shared_ptr<string> local_ptr = global_ptr;
local_ptr.reset();
The third aspect is that the managed object in the shared_ptr might or might not be thread safe. For example, I would say there is a data race in the following situation:
第三个方面是 shared_ptr 中的托管对象可能是也可能不是线程安全的。例如,我会说在以下情况下存在数据竞争:
# Main Thread
shared_ptr<string> global_ptr = make_shared<string>();
string str = *global_ptr;
# Thread 1
shared_ptr<string> local_ptr = global_ptr;
(*local_ptr).clear();
参考
https://gcc.gnu.org/onlinedocs/libstdc++/manual/memory.html#shared_ptr.thread
https://gcc.gnu.org/onlinedocs/libstdc++/manual/memory.html#shared_ptr.thread