С++ 11 безопасность потока общего указателя нарушена?

Согласно документации C++, блок управления shared_ptr является потокобезопасным. . т. е. операторы = или reset доступны нескольким потокам без явной блокировки.
Но я вижу странное поведение; общий объект иногда освобождается дважды:

#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;
}

Вот вывод Address Sanitizer:

==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++?


person Vinod Kd    schedule 11.02.2017    source источник


Ответы (1)


Нет. = и reset не являются потокобезопасными. Необходимы shared_ptr перегрузки std::atomic_... функций.

«Блок управления потокобезопасен» означает, что вы можете использовать = и reset в нескольких потоках (но каждый поток использует отдельный shared_ptr), даже если все объекты shared_ptr являются копиями друг друга.

person cshu    schedule 11.02.2017
comment
std::mutex может предоставить лучшее решение, чем std::atomic<std::shared_ptr>. - person Potatoswatter; 11.02.2017
comment
Что это обозначает? если sptr1=sptr2=make_shared‹MyClass›(b), сколько блоков управления будет? Только один.. верно? Если это не потокобезопасно, какой смысл использовать общий указатель. Не превратит ли это жизнь разработчика в кошмар? В случае обычных указателей разработчик может управлять уничтожением управляемого объекта. Но shared_ptr делает это неопределенным. - person Vinod Kd; 11.02.2017
comment
@VinodKd Да, будет только один. Но вам нужно скопировать shared_ptr до начала потока (или передать как аргумент), а не после. - person cshu; 12.02.2017