Keamanan utas penunjuk bersama C++11 rusak?

Sesuai dengan dokumentasi C++, blok kontrol dari shared_ptr aman untuk thread . yaitu, operator= atau reset dapat diakses oleh banyak thread tanpa penguncian eksplisit.
Namun saya melihat perilaku yang aneh; objek bersama kadang-kadang dibebaskan ganda:

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

Berikut ini keluaran 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 dan LLVM-3.9 menunjukkan perilaku yang sama. Apakah ini bug di perpustakaan C++?


person Vinod Kd    schedule 11.02.2017    source sumber


Jawaban (1)


No. = dan reset tidak aman untuk thread. Fungsi shared_ptr yang berlebihan diperlukan.

"Blok kontrol aman untuk thread" berarti Anda dapat menggunakan = dan reset di beberapa thread (tetapi setiap thread menggunakan shared_ptr terpisah), meskipun semua objek shared_ptr adalah salinan satu sama lain.

person cshu    schedule 11.02.2017
comment
std::mutex mungkin memberikan solusi yang lebih baik daripada std::atomic<std::shared_ptr>. - person Potatoswatter; 11.02.2017
comment
Maksudnya itu apa? jika sptr1=sptr2=make_shared‹Kelas Saya›(b), berapa banyak blok kontrol yang ada? Hanya satu.. kan? Jika itu tidak aman untuk thread, apa gunanya menggunakan pointer bersama. Bukankah itu akan membuat hidup pengembang menjadi mimpi buruk? Dalam hal petunjuk biasa, pengembang dapat mengontrol penghancuran objek yang dikelola. Tapi shared_ptr membuatnya tidak pasti. - person Vinod Kd; 11.02.2017
comment
@VinodKd Ya, hanya akan ada satu. Namun Anda perlu menyalin shared_ptr sebelum thread dimulai (atau diteruskan sebagai argumen), bukan setelah. - person cshu; 12.02.2017