Saya dengar itu const
berarti thread-safe di C ++ 11 . Benarkah itu?
Itu agak benar ...
Inilah yang dikatakan Bahasa Standar tentang keamanan thread:
[1.10 / 4]
Dua evaluasi ekspresi konflik jika salah satunya mengubah lokasi memori (1.7) dan yang lainnya mengakses atau mengubah lokasi memori yang sama.
[1.10 / 21]
Eksekusi program berisi balapan data jika berisi dua tindakan yang saling bertentangan di utas berbeda, setidaknya salah satunya tidak atom, dan tidak ada yang terjadi sebelum yang lain. Setiap data race menghasilkan perilaku yang tidak terdefinisi.
yang tidak lain adalah kondisi yang cukup untuk terjadinya data race :
- Ada dua atau lebih tindakan yang dilakukan pada waktu yang sama pada suatu hal; dan
- Setidaknya salah satunya adalah menulis.
The Library Standard dibangun di atas itu, akan sedikit lebih jauh:
[17.6.5.9/1]
Bagian ini menetapkan persyaratan yang harus dipenuhi oleh implementasi untuk mencegah balapan data (1.10). Setiap fungsi perpustakaan standar harus memenuhi setiap persyaratan kecuali ditentukan lain. Penerapan dapat mencegah data race dalam kasus selain yang ditentukan di bawah ini.
[17.6.5.9/3]
Fungsi pustaka standar C ++ tidak boleh secara langsung atau tidak langsung mengubah objek (1.10) yang dapat diakses oleh utas selain utas saat ini kecuali objek diakses secara langsung atau tidak langsung melaluiargumennon- konst fungsi, termasukthis
.
yang dengan kata sederhana mengatakan bahwa ia mengharapkan operasi pada const
objek menjadi thread-safe . Ini berarti bahwa Standard Library tidak akan memperkenalkan data race selama operasi pada const
objek jenis Anda juga
- Sepenuhnya terdiri dari bacaan --yaitu, tidak ada tulisan--; atau
- Menyinkronkan penulisan secara internal.
Jika harapan ini tidak berlaku untuk salah satu tipe Anda, maka menggunakannya secara langsung atau tidak langsung bersama dengan komponen Perpustakaan Standar dapat mengakibatkan perlombaan data . Kesimpulannya, const
berarti thread-safe dari sudut pandang Standard Library . Penting untuk dicatat bahwa ini hanyalah sebuah kontrak dan tidak akan diberlakukan oleh kompiler, jika Anda melanggarnya, Anda mendapatkan perilaku yang tidak terdefinisi dan Anda sendirian. Apakah const
hadir atau tidak tidak akan mempengaruhi generasi kode --at setidaknya tidak dalam hal ras data yang -.
Apakah itu berarti const
sekarang setara dengan Java 's synchronized
?
Tidak . Tidak semuanya...
Pertimbangkan kelas yang terlalu disederhanakan berikut ini yang merepresentasikan persegi panjang:
class rect {
int width = 0, height = 0;
public:
/*...*/
void set_size( int new_width, int new_height ) {
width = new_width;
height = new_height;
}
int area() const {
return width * height;
}
};
The anggota-fungsi area
adalah benang-aman ; bukan karena itu const
, tetapi karena seluruhnya terdiri dari operasi baca. Tidak ada penulisan yang terlibat, dan setidaknya satu penulisan yang terlibat diperlukan agar perlombaan data terjadi. Itu berarti Anda dapat memanggil area
dari sebanyak mungkin utas yang Anda inginkan dan Anda akan mendapatkan hasil yang benar setiap saat.
Catatan bahwa ini tidak berarti bahwa rect
adalah benang-aman . Faktanya, mudah untuk melihat bagaimana jika panggilan ke area
terjadi pada saat yang sama dengan panggilan ke set_size
yang diberikan rect
, kemudian area
dapat menghitung hasilnya berdasarkan lebar lama dan tinggi baru (atau bahkan pada nilai yang kacau) .
Tapi tidak apa-apa, rect
bukankah const
itu bahkan diharapkan tidak aman untuk thread sama sekali. const rect
Sebaliknya, objek yang dideklarasikan akan aman untuk thread karena tidak ada penulisan yang memungkinkan (dan jika Anda mempertimbangkan const_cast
-ing sesuatu yang awalnya dideklarasikan const
maka Anda mendapatkan perilaku yang tidak ditentukan dan hanya itu).
Jadi apa artinya itu?
Mari kita asumsikan - demi argumen - bahwa operasi perkalian sangat mahal dan sebaiknya kita menghindarinya jika memungkinkan. Kami dapat menghitung area hanya jika diminta, lalu menyimpannya dalam cache jika diminta lagi di masa mendatang:
class rect {
int width = 0, height = 0;
mutable int cached_area = 0;
mutable bool cached_area_valid = true;
public:
/*...*/
void set_size( int new_width, int new_height ) {
cached_area_valid = ( width == new_width && height == new_height );
width = new_width;
height = new_height;
}
int area() const {
if( !cached_area_valid ) {
cached_area = width;
cached_area *= height;
cached_area_valid = true;
}
return cached_area;
}
};
[Jika contoh ini tampak terlalu artifisial, Anda dapat menggantinya secara mental int
dengan bilangan bulat yang dialokasikan secara dinamis yang sangat besar yang secara inheren tidak aman untuk utas dan yang perkaliannya sangat mahal.]
Fungsi anggota area
tidak lagi aman untuk thread , sekarang sedang menulis dan tidak disinkronkan secara internal. Apakah ini masalah? Panggilan ke area
dapat terjadi sebagai bagian dari konstruktor salinan objek lain, konstruktor tersebut dapat dipanggil oleh beberapa operasi pada wadah standar , dan pada saat itu pustaka standar mengharapkan operasi ini berperilaku sebagai pembacaan dalam kaitannya dengan balapan data . Tapi kami sedang menulis!
Segera setelah kami menempatkan rect
dalam wadah standar --directly atau indirectly-- kita memasuki kontrak dengan Standard Library . Untuk terus melakukan penulisan dalam suatu const
fungsi sambil tetap mematuhi kontrak tersebut, kita perlu menyinkronkan penulisan tersebut secara internal:
class rect {
int width = 0, height = 0;
mutable std::mutex cache_mutex;
mutable int cached_area = 0;
mutable bool cached_area_valid = true;
public:
/*...*/
void set_size( int new_width, int new_height ) {
if( new_width != width || new_height != height )
{
std::lock_guard< std::mutex > guard( cache_mutex );
cached_area_valid = false;
}
width = new_width;
height = new_height;
}
int area() const {
std::lock_guard< std::mutex > guard( cache_mutex );
if( !cached_area_valid ) {
cached_area = width;
cached_area *= height;
cached_area_valid = true;
}
return cached_area;
}
};
Perhatikan bahwa kami membuat area
fungsi thread-safe , tetapi rect
masih belum aman untuk thread . Panggilan untuk area
terjadi pada saat yang sama dengan panggilan ke set_size
mungkin masih menghitung nilai yang salah, karena penugasan ke width
dan height
tidak dilindungi oleh mutex.
Jika kita benar-benar menginginkan thread-safe rect
, kita akan menggunakan sinkronisasi primitif untuk melindungi non-thread-safe rect
.
Apakah mereka kehabisan kata kunci ?
Ya begitulah. Mereka telah kehabisan kata kunci sejak hari pertama.
Sumber : Anda tidak tahu const
danmutable
- Herb Sutter