2017-02-11 269 views
1

根据C++ documentation,shared_ptr的控制块是线程安全的。即operator =或者reset可以在没有显式锁定的情况下被多个线程访问。
但我看到一种奇怪的行为;共享对象被双偶尔释放:C++ 11共享指针线程安全性是否被破坏?

#include <iostream> 
#include <memory> 
#include <unistd.h> 

using namespace std; 

class MyClass { 
public: 
    MyClass(string x) : name(x) {cout<<"C->"<<name<<endl;}; 
    ~MyClass() {cerr<<"D->"<<name<<endl;}; 
    string name; 
}; 

shared_ptr<MyClass> a; 

void* tfunc(void*) { 
    while(1){ 
    { 
     shared_ptr<MyClass> s = a; 
     usleep(5); 
    } 
    } 
} 

int main(void) { 
    pthread_t threadid; 
    a = make_shared<MyClass>("a"); 
    if(pthread_create(&threadid, NULL, tfunc, NULL)) { 
     cout<<"pthread_create error"<<endl; 
     return -1; 
    } 
    while(1){ 
     usleep(5); 
     a = make_shared<MyClass>("b"); 
    } 

    pthread_join(threadid, NULL); 
    return 0; 
} 

这里是消毒剂输出的地址:

==28588==ERROR: AddressSanitizer: heap-use-after-free on address 0xb33a7ad4 at pc 0x080490c4 bp 0xb54ff1d8 sp 0xb54ff1c8 
WRITE of size 4 at 0xb33a7ad4 thread T1 
    #0 0x80490c3 in __exchange_and_add /usr/include/c++/6/ext/atomicity.h:49 
    #1 0x80491ed in __exchange_and_add_dispatch /usr/include/c++/6/ext/atomicity.h:82 
    #2 0x8049a9e in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() /usr/include/c++/6/bits/shared_ptr_base.h:147 
    #3 0x80498a3 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() /usr/include/c++/6/bits/shared_ptr_base.h:662 
    #4 0x804977e in std::__shared_ptr<MyClass, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() /usr/include/c++/6/bits/shared_ptr_base.h:928 
    #5 0x8049797 in std::shared_ptr<MyClass>::~shared_ptr() /usr/include/c++/6/bits/shared_ptr.h:93 
    #6 0x80492d7 in tfunc(void*) /tmp/main.cpp:19 
    #7 0xb7248c4e (/usr/lib/i386-linux-gnu/libasan.so.3+0x26c4e) 
    #8 0xb6ea5304 in start_thread (/lib/i386-linux-gnu/libpthread.so.0+0x6304) 
    #9 0xb6fb347d in __clone (/lib/i386-linux-gnu/libc.so.6+0xe947d) 

0xb33a7ad4 is located 4 bytes inside of 36-byte region [0xb33a7ad0,0xb33a7af4) 
freed by thread T0 here: 
    #0 0xb72e7174 in operator delete(void*) (/usr/lib/i386-linux-gnu/libasan.so.3+0xc5174) 
    #1 0x804ace0 in __gnu_cxx::new_allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<MyClass>, (__gnu_cxx::_Lock_policy)2> >::deallocate(std::_Sp_counted_ptr_inplace<MyClass, std::allocator<MyClass>, (__gnu_cxx::_Lock_policy)2>*, unsigned int) /usr/include/c++/6/ext/new_allocator.h:110 
    #2 0x804ab07 in std::allocator_traits<std::allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<MyClass>, (__gnu_cxx::_Lock_policy)2> > >::deallocate(std::allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<MyClass>, (__gnu_cxx::_Lock_policy)2> >&, std::_Sp_counted_ptr_inplace<MyClass, std::allocator<MyClass>, (__gnu_cxx::_Lock_policy)2>*, unsigned int) /usr/include/c++/6/bits/alloc_traits.h:442 
    #3 0x804a818 in std::__allocated_ptr<std::allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<MyClass>, (__gnu_cxx::_Lock_policy)2> > >::~__allocated_ptr() /usr/include/c++/6/bits/allocated_ptr.h:73 
    #4 0x804b0aa in std::_Sp_counted_ptr_inplace<MyClass, std::allocator<MyClass>, (__gnu_cxx::_Lock_policy)2>::_M_destroy() /usr/include/c++/6/bits/shared_ptr_base.h:537 
    #5 0x8049bbe in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() /usr/include/c++/6/bits/shared_ptr_base.h:166 
    #6 0x80498a3 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() /usr/include/c++/6/bits/shared_ptr_base.h:662 
    #7 0x804977e in std::__shared_ptr<MyClass, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() /usr/include/c++/6/bits/shared_ptr_base.h:928 
    #8 0x8049cd4 in std::__shared_ptr<MyClass, (__gnu_cxx::_Lock_policy)2>::operator=(std::__shared_ptr<MyClass, (__gnu_cxx::_Lock_policy)2>&&) /usr/include/c++/6/bits/shared_ptr_base.h:1003 
    #9 0x8049a7c in std::shared_ptr<MyClass>::operator=(std::shared_ptr<MyClass>&&) /usr/include/c++/6/bits/shared_ptr.h:294 
    #10 0x8049430 in main /tmp/main.cpp:35 
    #11 0xb6ee2275 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18275) 

GCC-6.2和LLVM-3.9显示相同的行为。它是C++库中的一个错误吗?

回答

1

编号=reset不是线程安全的。需要std::atomic_...功能的shared_ptr过载。

“控制块是线程安全”意味着您可以使用多个线程=reset(但每个线程使用一个单独的shared_ptr),即使所有shared_ptr对象是相互的副本。

+0

'std :: mutex'可能比'std :: atomic '提供更好的解决方案。 – Potatoswatter

+0

这是什么意思?如果sptr1 = sptr2 = make_shared (“b”),将有多少个控制块?只有一个..对吗? 如果这不是线程安全的,使用共享指针有什么意义。它不会让开发者的生活成为一场噩梦吗? 如果使用常规指针,开发人员可以控制被管理对象的销毁。但shared_ptr使其不确定。 –

+0

@VinodKd是的,只会有一个。但是你需要在**线程开始之前(或作为参数传递)复制'shared_ptr' **,而不是在**之后。 – cshu