std :: shared_ptr thread safety menjelaskan

106

Saya membaca http://gcc.gnu.org/onlinedocs/libstdc++/manual/shared_ptr.html dan beberapa masalah keamanan untaian masih belum jelas bagi saya:

  1. Standar menjamin bahwa penghitungan referensi ditangani dengan thread safe dan platform independen, bukan?
  2. Masalah serupa - jaminan standar bahwa hanya satu utas (memegang referensi terakhir) yang akan memanggil hapus pada objek bersama, bukan?
  3. shared_ptr tidak menjamin keamanan utas untuk objek yang disimpan di dalamnya?

EDIT:

Kode semu:

// Thread I
shared_ptr<A> a (new A (1));

// Thread II
shared_ptr<A> b (a);

// Thread III
shared_ptr<A> c (a);

// Thread IV
shared_ptr<A> d (a);

d.reset (new A (10));

Memanggil reset () di thread IV akan menghapus instance sebelumnya dari kelas A yang dibuat di thread pertama dan menggantinya dengan instance baru? Selain itu setelah memanggil reset () di thread IV, thread lain hanya akan melihat objek yang baru dibuat?

Bodoh
sumber
24
Benar, benar, dan benar.
spraff
16
Anda harus menggunakan make_sharedbukannyanew
QDII

Jawaban:

87

Seperti yang ditunjukkan orang lain, Anda telah menemukannya dengan benar terkait 3 pertanyaan asli Anda.

Tapi bagian akhir dari suntingan Anda

Memanggil reset () di thread IV akan menghapus instance sebelumnya dari kelas A yang dibuat di thread pertama dan menggantinya dengan instance baru? Selain itu setelah memanggil reset () di thread IV, thread lain hanya akan melihat objek yang baru dibuat?

salah. Hanya dakan mengarah ke baru A(10), dan a, bdan cakan terus ke titik dengan aslinya A(1). Hal ini terlihat jelas pada contoh singkat berikut ini.

#include <memory>
#include <iostream>
using namespace std;

struct A
{
  int a;
  A(int a) : a(a) {}
};

int main(int argc, char **argv)
{
  shared_ptr<A> a(new A(1));
  shared_ptr<A> b(a), c(a), d(a);

  cout << "a: " << a->a << "\tb: " << b->a
     << "\tc: " << c->a << "\td: " << d->a << endl;

  d.reset(new A(10));

  cout << "a: " << a->a << "\tb: " << b->a
     << "\tc: " << c->a << "\td: " << d->a << endl;
                                                                                                                 
  return 0;                                                                                                          
}

(Jelas, saya tidak peduli dengan threading apa pun: itu tidak menjadi faktor dalam shared_ptr::reset()perilaku.)

Output dari kode ini adalah

a: 1 b: 1 c: 1 d: 1

a: 1 b: 1 c: 1 d: 10

Nicu Stiurca
sumber
35
  1. Benar, shared_ptrgunakan kenaikan / penurunan atom dari nilai hitungan referensi.

  2. Standar menjamin hanya satu utas yang akan memanggil operator hapus pada objek bersama. Saya tidak yakin apakah itu secara khusus menentukan utas terakhir yang menghapus salinan penunjuk bersama akan menjadi yang memanggil hapus (kemungkinan dalam praktiknya ini akan terjadi).

  3. Tidak, objek yang disimpan di dalamnya dapat diedit secara bersamaan oleh beberapa utas.

EDIT: Sedikit tindak lanjut, jika Anda ingin mendapatkan gambaran tentang bagaimana petunjuk bersama bekerja secara umum, Anda mungkin ingin melihat boost::shared_ptrsumbernya: http://www.boost.org/doc/libs/1_37_0/boost/shared_ptr.hpp .

Tidak ada lagi
sumber
3
1. Saat Anda mengatakan "'shared_ptrs' use atomic increments / decrements dari sebuah referensi count value." Apakah maksud Anda mereka tidak menggunakan kunci internal apa pun untuk kenaikan / penurunan atom, yang konteksnya beralih? Dalam bahasa sederhana, apakah beberapa utas dapat menambah / mengurangi jumlah referensi tanpa menggunakan kunci? Sebuah kenaikan atom dilakukan dengan instruksi atomic_test_and_swap / atomic_test_and_increment khusus?
rahul.deshmukhpatil
@rahul kompiler bebas menggunakan mutex / kunci, tetapi kebanyakan kompiler yang baik tidak akan menggunakan mutex / kunci pada platform yang dapat dilakukan tanpa kunci.
Bernard
@Bernard: maksud Anda itu tergantung pada implementasi "compiler std lib shared_ptr" untuk platform?
rahul.deshmukhpatil
2
Iya. Dari pemahaman saya, standar tidak mengatakan bahwa itu harus bebas kunci. Tapi di GCC dan MSVC terbaru, ini bebas kunci pada perangkat keras Intel x86, dan saya pikir kompiler bagus lainnya cenderung melakukan hal yang sama ketika perangkat keras mendukungnya.
Bernard
18

std::shared_ptr tidak aman untuk benang.

Penunjuk bersama adalah sepasang dua penunjuk, satu ke objek dan satu ke blok kontrol (menahan penghitung referensi, tautan ke penunjuk lemah ...).

Ada beberapa std :: shared_ptr dan kapan pun mereka mengakses blok kontrol untuk mengubah penghitung referensi, ini aman untuk thread tetapi std::shared_ptritu sendiri TIDAK aman untuk thread atau atomic.

Jika Anda menetapkan objek baru untuk std::shared_ptrsementara utas lain menggunakannya, itu mungkin berakhir dengan penunjuk objek baru tetapi masih menggunakan penunjuk ke blok kontrol dari objek lama => CRASH.

Lothar
sumber
4
Kita dapat mengatakan bahwa satu std::shared_ptrcontoh tidak aman untuk benang. Dari referensi std :: shared_ptr:If multiple threads of execution access the same shared_ptr without synchronization and any of those accesses uses a non-const member function of shared_ptr then a data race will occur;
JKovalsky
Ini bisa dikatakan lebih baik. Sebuah std::shared_ptr<T>instance dijamin aman untuk thread ketika selalu digunakan oleh nilai (disalin / dipindahkan) melintasi batas thread. Semua penggunaan lain, std::shared_ptr<T>&tidak aman melintasi batas utas
WhiZTiM