Hanya menambahkan jawaban ini karena menurut saya jawaban yang diterima mungkin menyesatkan. Dalam semua kasus, Anda perlu mengunci mutex, sebelum memanggil notify_one () di suatu tempat agar kode Anda menjadi thread-safe, meskipun Anda mungkin membukanya lagi sebelum benar-benar memanggil notify _ * ().
Untuk memperjelas, Anda HARUS membuka kunci sebelum memasuki tunggu (lk) karena wait () membuka kunci lk dan ini akan menjadi Perilaku Tidak Terdefinisi jika kunci tidak terkunci. Ini tidak terjadi dengan notify_one (), tetapi Anda perlu memastikan bahwa Anda tidak akan memanggil notify _ * () sebelum memasuki wait () dan meminta panggilan itu membuka kunci mutex; yang jelas hanya dapat dilakukan dengan mengunci mutex yang sama sebelum Anda memanggil notify _ * ().
Misalnya, perhatikan kasus berikut:
std::atomic_int count;
std::mutex cancel_mutex;
std::condition_variable cancel_cv;
void stop()
{
if (count.fetch_sub(1) == -999)
cv.notify_one();
}
bool start()
{
if (count.fetch_add(1) >= 0)
return true;
stop();
return false;
}
void cancel()
{
if (count.fetch_sub(1000) == 0)
return;
std::unique_lock<std::mutex> lk(cancel_mutex);
cancel_cv.wait(lk);
}
Peringatan : kode ini mengandung bug.
Idenya adalah sebagai berikut: thread memanggil start () dan stop () berpasangan, tetapi hanya selama start () mengembalikan true. Sebagai contoh:
if (start())
{
stop();
}
Satu utas (lainnya) di beberapa titik akan memanggil cancel () dan setelah kembali dari cancel () akan menghancurkan objek yang diperlukan di 'Lakukan barang'. Namun, cancel () seharusnya tidak kembali saat ada utas antara start () dan stop (), dan setelah cancel () dieksekusi baris pertamanya, start () akan selalu mengembalikan false, jadi tidak ada utas baru yang akan masuk ke 'Do area barang.
Bekerja dengan benar?
Alasannya adalah sebagai berikut:
1) Jika ada utas yang berhasil mengeksekusi baris pertama start () (dan karena itu akan mengembalikan true) maka belum ada utas yang mengeksekusi baris pertama cancel () (kami berasumsi bahwa jumlah total utas jauh lebih kecil dari 1000 oleh cara).
2) Juga, ketika sebuah utas berhasil mengeksekusi baris pertama start (), tetapi belum menjadi baris pertama stop () maka tidak mungkin utas apa pun akan berhasil mengeksekusi baris pertama cancel () (perhatikan bahwa hanya satu utas pernah memanggil cancel ()): nilai yang dikembalikan oleh fetch_sub (1000) akan lebih besar dari 0.
3) Setelah utas mengeksekusi baris pertama batal (), baris pertama start () akan selalu mengembalikan false dan utas yang memanggil start () tidak akan masuk ke area 'Lakukan barang' lagi.
4) Jumlah panggilan ke start () dan stop () selalu seimbang, jadi setelah baris pertama batal () tidak berhasil dijalankan, akan selalu ada momen di mana panggilan (terakhir) ke stop () menyebabkan hitungan untuk mencapai -1000 dan karena itu notify_one () akan dipanggil. Perhatikan bahwa hal itu hanya dapat terjadi jika baris pertama pembatalan mengakibatkan thread tersebut gagal.
Terlepas dari masalah kelaparan di mana begitu banyak utas memanggil start () / stop () yang jumlahnya tidak pernah mencapai -1000 dan cancel () tidak pernah kembali, yang mungkin diterima sebagai "tidak mungkin dan tidak pernah bertahan lama", ada bug lain:
Ada kemungkinan bahwa ada satu utas di dalam area 'Lakukan barang', katakanlah itu hanya memanggil stop (); pada saat itu utas mengeksekusi baris pertama cancel () yang membaca nilai 1 dengan fetch_sub (1000) dan gagal. Tetapi sebelum mengambil mutex dan / atau melakukan panggilan untuk menunggu (lk), utas pertama mengeksekusi baris pertama dari stop (), membaca -999 dan memanggil cv.notify_one ()!
Kemudian panggilan ke notify_one () ini dilakukan SEBELUM kita menunggu () - menggunakan variabel kondisi! Dan program itu akan mati tanpa batas.
Untuk alasan ini kita seharusnya tidak dapat memanggil notify_one () sampai kita memanggil wait (). Perhatikan bahwa kekuatan variabel kondisi terletak di sana sehingga ia dapat membuka kunci mutex secara atom, periksa apakah panggilan ke notify_one () terjadi dan masuk ke mode tidur atau tidak. Anda tidak bisa menipu, tapi Anda lakukan perlu untuk menjaga mutex terkunci setiap kali Anda membuat perubahan pada variabel yang mungkin berubah kondisi dari false ke true dan tetap terkunci sambil menelepon notify_one () karena kondisi balapan seperti dijelaskan di sini.
Namun dalam contoh ini tidak ada syarat. Mengapa saya tidak menggunakan sebagai kondisi 'count == -1000'? Karena itu sama sekali tidak menarik di sini: segera setelah -1000 tercapai, kami yakin tidak ada utas baru yang akan memasuki area 'Lakukan barang'. Selain itu, utas masih dapat memanggil start () dan akan menambah jumlah (menjadi -999 dan -998 dll) tetapi kami tidak peduli tentang itu. Satu-satunya hal yang penting adalah bahwa -1000 tercapai - sehingga kami tahu pasti bahwa tidak ada lagi utas di area 'Lakukan barang'. Kami yakin bahwa ini adalah kasus ketika notify_one () dipanggil, tetapi bagaimana memastikan kami tidak memanggil notify_one () sebelum cancel () mengunci mutexnya? Hanya mengunci cancel_mutex sesaat sebelum notify_one () tidak akan membantu tentunya.
Masalahnya, meski kita tidak menunggu kondisi, tetap ada syarat, dan kita perlu mengunci mutex.
1) sebelum kondisi itu tercapai 2) sebelum kita memanggil notify_one.
Oleh karena itu, kode yang benar menjadi:
void stop()
{
if (count.fetch_sub(1) == -999)
{
cancel_mutex.lock();
cancel_mutex.unlock();
cv.notify_one();
}
}
[... awal yang sama () ...]
void cancel()
{
std::unique_lock<std::mutex> lk(cancel_mutex);
if (count.fetch_sub(1000) == 0)
return;
cancel_cv.wait(lk);
}
Tentu saja ini hanyalah satu contoh tetapi kasus-kasus lain sangat mirip; dalam hampir semua kasus di mana Anda menggunakan variabel kondisional, Anda harus memiliki mutex yang dikunci (segera) sebelum memanggil notify_one (), atau mungkin Anda memanggilnya sebelum memanggil wait ().
Perhatikan bahwa saya membuka kunci mutex sebelum memanggil notify_one () dalam kasus ini, karena jika tidak ada kemungkinan (kecil) bahwa panggilan ke notify_one () membangunkan utas menunggu variabel kondisi yang kemudian akan mencoba untuk mengambil mutex dan blokir, sebelum kita melepaskan mutex lagi. Itu hanya sedikit lebih lambat dari yang dibutuhkan.
Contoh ini agak istimewa karena baris yang mengubah kondisi dieksekusi oleh utas yang sama yang memanggil wait ().
Yang lebih umum adalah kasus di mana satu utas hanya menunggu kondisi menjadi benar dan utas lain mengambil kunci sebelum mengubah variabel yang terlibat dalam kondisi itu (menyebabkannya mungkin menjadi benar). Dalam hal mutex yang terkunci segera sebelum (dan setelah) kondisi menjadi benar - sehingga benar-benar ok untuk hanya membuka mutex sebelum memanggil memberitahukan _ * () dalam kasus itu.
wait morphing
pengoptimalan) Aturan praktis yang dijelaskan di tautan ini: beri tahu DENGAN kunci lebih baik dalam situasi dengan lebih dari 2 utas untuk hasil yang lebih dapat diprediksi.