Apakah const berarti thread-safe di C ++ 11?

116

Saya dengar itu constberarti thread-safe di C ++ 11 . Benarkah itu?

Apakah itu berarti constsekarang setara dengan Java 's synchronized?

Apakah mereka kehabisan kata kunci ?

K-ballo
sumber
1
C ++ - faq umumnya dikelola oleh komunitas C ++, dan Anda dapat datang dan meminta pendapat kami di obrolan kami.
Anak Anjing
@DeadMG: Saya tidak mengetahui C ++ - faq dan etiketnya, itu disarankan dalam komentar.
K-ballo
2
Di mana Anda mendengar bahwa const berarti thread-safe?
Mark B
2
@ Mark B: Herb Sutter dan Bjarne Stroustrup mengatakannya di Standard C ++ Foundation , lihat tautan di bagian bawah jawaban.
K-ballo
CATATAN BAGI MEREKA YANG DATANG KE SINI: pertanyaan sebenarnya BUKANLAH apakah const berarti thread-safe. Itu tidak masuk akal, karena jika tidak, itu berarti Anda harus dapat melanjutkan dan menandai setiap metode aman utas sebagai const. Sebaliknya, pertanyaan yang sebenarnya kami tanyakan adalah const IMPLIES thread-safe, dan itulah inti diskusi ini.
pengguna541686

Jawaban:

132

Saya dengar itu constberarti 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 :

  1. Ada dua atau lebih tindakan yang dilakukan pada waktu yang sama pada suatu hal; dan
  2. 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 constobjek menjadi thread-safe . Ini berarti bahwa Standard Library tidak akan memperkenalkan data race selama operasi pada constobjek jenis Anda juga

  1. Sepenuhnya terdiri dari bacaan --yaitu, tidak ada tulisan--; atau
  2. 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, constberarti 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 consthadir atau tidak tidak akan mempengaruhi generasi kode --at setidaknya tidak dalam hal ras data yang -.

Apakah itu berarti constsekarang 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 areadari sebanyak mungkin utas yang Anda inginkan dan Anda akan mendapatkan hasil yang benar setiap saat.

Catatan bahwa ini tidak berarti bahwa rectadalah benang-aman . Faktanya, mudah untuk melihat bagaimana jika panggilan ke areaterjadi pada saat yang sama dengan panggilan ke set_sizeyang diberikan rect, kemudian areadapat menghitung hasilnya berdasarkan lebar lama dan tinggi baru (atau bahkan pada nilai yang kacau) .

Tapi tidak apa-apa, rectbukankah constitu bahkan diharapkan tidak aman untuk thread sama sekali. const rectSebaliknya, objek yang dideklarasikan akan aman untuk thread karena tidak ada penulisan yang memungkinkan (dan jika Anda mempertimbangkan const_cast-ing sesuatu yang awalnya dideklarasikan constmaka 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 intdengan 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 areadapat 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 rectdalam wadah standar --directly atau indirectly-- kita memasuki kontrak dengan Standard Library . Untuk terus melakukan penulisan dalam suatu constfungsi 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 areafungsi thread-safe , tetapi rectmasih belum aman untuk thread . Panggilan untuk areaterjadi pada saat yang sama dengan panggilan ke set_sizemungkin masih menghitung nilai yang salah, karena penugasan ke widthdan heighttidak 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 constdanmutable - Herb Sutter

K-ballo
sumber
6
@ Ben Voigt: Menurut pemahaman saya, spesifikasi C ++ 11 untuk std::stringworded dengan cara yang sudah melarang KK . Saya tidak ingat secara spesifik, meskipun ...
K-ballo
3
@BenVoigt: Tidak. Ini hanya akan mencegah hal-hal seperti itu tidak disinkronkan- yaitu, tidak aman untuk utas. C ++ 11 sudah secara eksplisit melarang KK- bagian khusus ini tidak ada hubungannya dengan itu, dan tidak akan melarang KK.
Anak Anjing
2
Menurut saya, ada celah logis. [17.6.5.9/3] melarang "terlalu banyak" dengan mengatakan "tidak akan secara langsung atau tidak langsung mengubah"; itu harus mengatakan "tidak akan secara langsung atau tidak langsung memperkenalkan perlombaan data", kecuali menulis atom di suatu tempat didefinisikan bukan sebagai "modifikasi". Tetapi saya tidak dapat menemukan ini di mana pun.
Andy Prowl
1
Saya mungkin membuat keseluruhan poin saya sedikit lebih jelas di sini: isocpp.org/blog/2012/12/… Terima kasih telah mencoba membantu.
Andy Prowl
1
Kadang-kadang saya bertanya-tanya siapa yang sebenarnya (atau yang terlibat langsung) yang sebenarnya bertanggung jawab untuk menuliskan beberapa paragraf standar seperti ini.
pepper_chico