ความปลอดภัยของเธรดตัวชี้ที่ใช้ร่วมกันของ C ++ 11 ใช้งานไม่ได้หรือไม่

ตามเอกสารประกอบ C++ บล็อกควบคุมของ shared_ptr นั้นปลอดภัยต่อเธรด . กล่าวคือ โอเปอเรเตอร์= หรือรีเซ็ตสามารถเข้าถึงได้จากหลายเธรดโดยไม่ต้องล็อคอย่างชัดเจน
แต่ฉันเห็นพฤติกรรมแปลกๆ วัตถุที่แชร์จะถูกปลดปล่อยเป็นสองเท่าในบางครั้ง:

#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