Apakah saya harus mendapatkan kunci sebelum memanggil condition_variable.notify_one ()?


90

Saya agak bingung tentang penggunaan std::condition_variable. Saya mengerti bahwa saya harus membuat unique_lockon a mutexsebelum menelepon condition_variable.wait(). Apa yang tidak dapat saya temukan adalah apakah saya juga harus mendapatkan kunci unik sebelum menelepon notify_one()atau notify_all().

Contoh di cppreference.com saling bertentangan. Misalnya, halaman notify_one memberikan contoh ini:

#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>

std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;

void waits()
{
    std::unique_lock<std::mutex> lk(cv_m);
    std::cout << "Waiting... \n";
    cv.wait(lk, []{return i == 1;});
    std::cout << "...finished waiting. i == 1\n";
    done = true;
}

void signals()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Notifying...\n";
    cv.notify_one();

    std::unique_lock<std::mutex> lk(cv_m);
    i = 1;
    while (!done) {
        lk.unlock();
        std::this_thread::sleep_for(std::chrono::seconds(1));
        lk.lock();
        std::cerr << "Notifying again...\n";
        cv.notify_one();
    }
}

int main()
{
    std::thread t1(waits), t2(signals);
    t1.join(); t2.join();
}

Di sini kunci tidak diperoleh untuk yang pertama notify_one(), tetapi diperoleh untuk yang kedua notify_one(). Melihat halaman-halaman lain dengan contoh-contoh saya melihat hal-hal yang berbeda, kebanyakan tidak memperoleh kunci.

  • Dapatkah saya memilih sendiri untuk mengunci mutex sebelum menelepon notify_one(), dan mengapa saya memilih untuk menguncinya?
  • Dalam contoh yang diberikan, mengapa tidak ada kunci untuk yang pertama notify_one(), tetapi ada untuk panggilan berikutnya. Apakah contoh ini salah atau ada alasannya?

Jawaban:


77

Anda tidak perlu memegang kunci saat menelepon condition_variable::notify_one(), tapi itu tidak salah dalam artian masih terdefinisi dengan baik dan bukan error.

Namun, ini mungkin sebuah "pesimisasi" karena thread menunggu apa pun yang dibuat dapat dijalankan (jika ada) akan segera mencoba untuk mendapatkan kunci yang dipegang oleh thread notifikasi. Saya pikir itu adalah aturan praktis yang baik untuk menghindari memegang kunci yang terkait dengan variabel kondisi saat memanggil notify_one()atau notify_all(). Lihat Pthread Mutex: pthread_mutex_unlock () menghabiskan banyak waktu untuk contoh di mana melepaskan kunci sebelum memanggil pthread yang setara dengan notify_one()peningkatan kinerja yang terukur.

Perlu diingat bahwa lock()panggilan dalam whileloop diperlukan di beberapa titik, karena kunci perlu ditahan selama while (!done)pemeriksaan kondisi loop. Tetapi tidak perlu ditahan untuk panggilan ke notify_one().


2016-02-27 : Pembaruan besar untuk menjawab beberapa pertanyaan di komentar tentang apakah ada kondisi balapan adalah kunci tidak membantu untuk notify_one()panggilan. Saya tahu pembaruan ini terlambat karena pertanyaan itu diajukan hampir dua tahun yang lalu, tetapi saya ingin menjawab pertanyaan @ Cookie tentang kemungkinan kondisi balapan jika produsen ( signals()dalam contoh ini) menelepon notify_one()tepat sebelum konsumen ( waits()dalam contoh ini) adalah bisa menelepon wait().

Kuncinya adalah apa yang terjadi i- itulah objek yang benar-benar menunjukkan apakah konsumen memiliki "pekerjaan" yang harus dilakukan atau tidak. Ini condition_variablehanyalah mekanisme untuk membiarkan konsumen secara efisien menunggu perubahan i.

Produsen perlu menahan kunci saat memperbarui i, dan konsumen harus memegang kunci saat memeriksa idan memanggil condition_variable::wait()(jika perlu menunggu sama sekali). Dalam hal ini, kuncinya adalah harus merupakan contoh yang sama dari memegang kunci (sering disebut bagian kritis) ketika konsumen melakukan pemeriksaan dan tunggu ini. Karena bagian kritis diadakan saat produsen memperbarui idan saat konsumen memeriksa dan menunggu i, tidak ada peluang untuk iberalih antara saat konsumen memeriksa idan saat menelepon condition_variable::wait(). Ini adalah inti dari penggunaan variabel kondisi dengan benar.

Standar C ++ mengatakan bahwa condition_variable :: wait () berperilaku seperti berikut saat dipanggil dengan predikat (seperti dalam kasus ini):

while (!pred())
    wait(lock);

Ada dua situasi yang dapat terjadi ketika konsumen memeriksa i:

  • jika i0 maka konsumen memanggil cv.wait(), maka iakan tetap 0 ketika wait(lock)bagian dari implementasi dipanggil - penggunaan yang tepat dari kunci memastikan itu. Dalam hal ini produsen tidak memiliki kesempatan untuk memanggil condition_variable::notify_one()dalam whileloopnya sampai setelah konsumen menelepon cv.wait(lk, []{return i == 1;})(dan wait()panggilan telah melakukan semua yang perlu dilakukan untuk 'menangkap' pemberitahuan dengan benar - wait()tidak akan melepaskan kunci sampai selesai melakukannya. ). Jadi dalam hal ini konsumen tidak dapat melewatkan notifikasi tersebut.

  • jika isudah 1 ketika konsumen memanggil cv.wait(), wait(lock)bagian dari implementasi tidak akan pernah dipanggil karena while (!pred())pengujian akan menyebabkan pengulangan internal berhenti. Dalam situasi ini, tidak masalah kapan panggilan ke notify_one () terjadi - konsumen tidak akan memblokir.

Contoh di sini memang memiliki kerumitan tambahan dalam menggunakan donevariabel untuk memberi sinyal kembali ke utas produsen yang telah dikenali oleh konsumen i == 1, tetapi saya rasa ini tidak mengubah analisis sama sekali karena semua akses ke done(untuk membaca dan memodifikasi ) dilakukan saat berada di bagian kritis yang sama yang melibatkan idan condition_variable.

Jika Anda melihat pertanyaan yang ditunjukkan oleh @ eh9, Sinkronisasi tidak dapat diandalkan menggunakan std :: atomic dan std :: condition_variable , Anda akan melihat kondisi pacu. Namun, kode yang diposting dalam pertanyaan itu melanggar salah satu aturan dasar penggunaan variabel kondisi: Ini tidak memiliki satu bagian penting saat melakukan pemeriksaan dan tunggu.

Dalam contoh itu, kodenya terlihat seperti:

if (--f->counter == 0)      // (1)
    // we have zeroed this fence's counter, wake up everyone that waits
    f->resume.notify_all(); // (2)
else
{
    unique_lock<mutex> lock(f->resume_mutex);
    f->resume.wait(lock);   // (3)
}

Anda akan melihat bahwa wait()pada # 3 dilakukan sambil menahan f->resume_mutex. Tetapi pemeriksaan apakah perlu atau tidak wait()pada langkah # 1 tidak dilakukan sambil menahan kunci itu sama sekali (apalagi terus menerus untuk pemeriksaan dan tunggu), yang merupakan persyaratan untuk penggunaan variabel kondisi yang tepat). Saya percaya bahwa orang yang memiliki masalah dengan potongan kode itu mengira bahwa karena f->countermerupakan std::atomictipe, ini akan memenuhi persyaratan. Namun, atomisitas yang diberikan oleh std::atomictidak mencakup panggilan berikutnya ke f->resume.wait(lock). Dalam contoh ini, ada balapan antara kapan f->counterdicentang (langkah # 1) dan kapan wait()dipanggil (langkah # 3).

Ras itu tidak ada dalam contoh pertanyaan ini.


2
ini memiliki implikasi yang lebih dalam: domaigne.com/blog/computing/… Khususnya, masalah pthread yang Anda sebutkan harus diselesaikan dengan versi yang lebih baru atau versi yang dibuat dengan flag yang benar. (untuk mengaktifkan wait morphingpengoptimalan) 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.
v.oddou

6
@ Michael: Untuk pemahaman saya, konsumen pada akhirnya harus menelepon the_condition_variable.wait(lock);. Jika tidak ada kunci yang diperlukan untuk menyinkronkan produsen dan konsumen (katakanlah yang mendasarinya adalah antrean spsc bebas kunci), maka kunci itu tidak berfungsi jika produsen tidak menguncinya. Baik oleh saya. Tapi bukankah ada risiko ras langka? Jika produser tidak memegang kuncinya, tidak bisakah dia menelepon notify_one sementara konsumen sebelum menunggu? Kemudian konsumen menunggu dan tidak akan bangun ...
Cookie

1
misalnya katakanlah dalam kode di atas konsumen berada pada std::cout << "Waiting... \n";saat produsen melakukannya cv.notify_one();, kemudian panggilan bangun hilang ... Atau apakah saya melewatkan sesuatu di sini?
Cookie

1
@Kue kering. Ya, Ada kondisi balapan di sana. Lihat stackoverflow.com/questions/20982270/…
eh9

1
@ eh9: Sial, saya baru saja menemukan penyebab bug membekukan kode saya dari waktu ke waktu berkat komentar Anda. Itu karena kasus kondisi balapan yang tepat. Membuka kunci mutex setelah notify sepenuhnya menyelesaikan masalah ... Terima kasih banyak!
galinette

10

Situasi

Menggunakan vc10 dan Boost 1.56 Saya menerapkan antrian bersamaan seperti yang disarankan posting blog ini . Penulis membuka mutex untuk meminimalkan pertentangan, yaitu notify_one()dipanggil dengan mutex yang tidak terkunci:

void push(const T& item)
{
  std::unique_lock<std::mutex> mlock(mutex_);
  queue_.push(item);
  mlock.unlock();     // unlock before notificiation to minimize mutex contention
  cond_.notify_one(); // notify one waiting thread
}

Membuka kunci mutex didukung oleh contoh di dokumentasi Boost :

void prepare_data_for_processing()
{
    retrieve_data();
    prepare_data();
    {
        boost::lock_guard<boost::mutex> lock(mut);
        data_ready=true;
    }
    cond.notify_one();
}

Masalah

Tetap saja hal ini menyebabkan perilaku tidak menentu berikut:

  • sementara notify_one()telah tidak disebut belum cond_.wait()masih dapat terganggu viaboost::thread::interrupt()
  • sekali notify_one()dipanggil untuk pertama kalinya cond_.wait()kebuntuan; penantian tidak dapat diakhiri oleh boost::thread::interrupt()atau boost::condition_variable::notify_*()lagi.

Larutan

Menghapus baris mlock.unlock()membuat kode berfungsi seperti yang diharapkan (pemberitahuan dan interupsi mengakhiri penantian). Perhatikan bahwa notify_one()dipanggil dengan mutex masih terkunci, itu akan dibuka kuncinya setelah itu ketika meninggalkan ruang lingkup:

void push(const T& item)
{
  std::lock_guard<std::mutex> mlock(mutex_);
  queue_.push(item);
  cond_.notify_one(); // notify one waiting thread
}

Itu berarti bahwa setidaknya dengan implementasi utas khusus saya, mutex tidak boleh dibuka sebelum memanggil boost::condition_variable::notify_one(), meskipun kedua cara tampak benar.


Apakah Anda melaporkan masalah ini ke Boost.Thread? Saya tidak dapat menemukan tugas serupa di sana svn.boost.org/trac/boost/…
magras

@magras Sayangnya saya tidak, tidak tahu mengapa saya tidak mempertimbangkan ini. Dan sayangnya saya tidak berhasil mereproduksi kesalahan ini menggunakan antrian yang disebutkan.
Matthäus Brandl

Saya tidak yakin saya melihat bagaimana bangun pagi dapat menyebabkan kebuntuan. Secara khusus, jika Anda keluar dari cond_.wait () di pop () setelah push () melepaskan antrian mutex tetapi sebelum notify_one () dipanggil - Pop () akan melihat antrian tidak kosong, dan mengkonsumsi entri baru daripada wait () ing. jika Anda keluar dari cond_.wait () saat push () memperbarui antrian, kunci harus ditahan oleh push (), sehingga pop () harus memblokir menunggu kunci dilepaskan. Bangun awal lainnya akan menahan kunci, menjaga push () dari memodifikasi antrian sebelum pop () memanggil wait () berikutnya. Apa yang saya lewatkan?
Kevin

4

Seperti yang ditunjukkan orang lain, Anda tidak perlu memegang kunci saat menelepon notify_one(), dalam hal kondisi balapan dan masalah terkait threading. Namun, dalam beberapa kasus, memegang kunci mungkin diperlukan untuk mencegah agar condition_variabletidak hancur sebelum notify_one()dipanggil. Perhatikan contoh berikut:

thread t;

void foo() {
    std::mutex m;
    std::condition_variable cv;
    bool done = false;

    t = std::thread([&]() {
        {
            std::lock_guard<std::mutex> l(m);  // (1)
            done = true;  // (2)
        }  // (3)
        cv.notify_one();  // (4)
    });  // (5)

    std::unique_lock<std::mutex> lock(m);  // (6)
    cv.wait(lock, [&done]() { return done; });  // (7)
}

void main() {
    foo();  // (8)
    t.join();  // (9)
}

Asumsikan ada sakelar konteks ke utas yang baru dibuat tsetelah kita membuatnya tetapi sebelum kita mulai menunggu variabel kondisi (di suatu tempat antara (5) dan (6)). Utas tmemperoleh kunci (1), mengatur variabel predikat (2) dan kemudian melepaskan kunci (3). Asumsikan ada sakelar konteks lain tepat pada titik ini sebelum notify_one()(4) dijalankan. Utas utama memperoleh kunci (6) dan mengeksekusi baris (7), pada titik mana predikat kembali truedan tidak ada alasan untuk menunggu, sehingga melepaskan kunci dan melanjutkan. fooreturn (8) dan variabel dalam ruang lingkupnya (termasuk cv) dimusnahkan. Sebelum thread tdapat bergabung dengan thread utama (9), ia harus menyelesaikan eksekusinya, jadi ia melanjutkan dari tempat ia tinggalkan untuk mengeksekusicv.notify_one()(4), di titik mana cvsudah hancur!

Perbaikan yang mungkin dalam kasus ini adalah untuk tetap menahan kunci saat memanggil notify_one(yaitu menghapus ruang lingkup yang berakhir pada baris (3)). Dengan demikian, kami memastikan bahwa tpemanggilan thread notify_onesebelumnya cv.waitdapat memeriksa variabel predikat yang baru disetel dan melanjutkan, karena variabel tersebut perlu memperoleh kunci, yang t saat ini ditahan, untuk melakukan pemeriksaan. Jadi, kami memastikan bahwa cvtidak diakses oleh utas tsetelah foopengembalian.

Untuk meringkas, masalah dalam kasus khusus ini bukanlah tentang threading, tetapi tentang masa pakai variabel yang ditangkap oleh referensi. cvditangkap dengan referensi melalui utas t, maka Anda harus memastikan cvtetap hidup selama eksekusi utas. Contoh lain yang disajikan di sini tidak mengalami masalah ini, karena condition_variabledan mutexobjek didefinisikan dalam cakupan global, maka mereka dijamin akan tetap hidup sampai program keluar.


1

@Michael Burr benar. condition_variable::notify_onetidak membutuhkan kunci pada variabel. Tidak ada yang menghalangi Anda untuk menggunakan kunci dalam situasi itu, seperti yang diilustrasikan dalam contoh.

Dalam contoh yang diberikan, kunci dimotivasi oleh penggunaan variabel secara bersamaan i. Karena signalsutas memodifikasi variabel, utas perlu memastikan bahwa tidak ada utas lain yang mengaksesnya selama waktu itu.

Kunci digunakan untuk situasi apa pun yang memerlukan sinkronisasi , saya rasa kami tidak dapat menyatakannya dengan cara yang lebih umum.


Tentu saja, tetapi di atas itu semua juga perlu digunakan bersama dengan variabel kondisi agar keseluruhan pola benar-benar berfungsi. Terutama waitfungsi variabel kondisi adalah melepaskan kunci di dalam panggilan, dan kembali hanya setelah itu telah memperoleh kembali kunci tersebut. setelah itu Anda dapat dengan aman memeriksa kondisi Anda karena Anda telah memperoleh "hak membaca", misalnya. jika itu masih bukan apa yang Anda tunggu, Anda kembali ke wait. ini polanya. btw, contoh ini TIDAK menghormatinya.
v.oddou

1

Dalam beberapa kasus, ketika cv mungkin ditempati (dikunci) oleh utas lain. Anda perlu mengunci dan melepaskannya sebelum memberi tahu _ * ().
Jika tidak, notify _ * () mungkin tidak dijalankan sama sekali.


1

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) // Reached -1000 ?
    cv.notify_one();
}

bool start()
{
  if (count.fetch_add(1) >= 0)
    return true;
  // Failure.
  stop();
  return false;
}

void cancel()
{
  if (count.fetch_sub(1000) == 0)  // Reached -1000?
    return;
  // Wait till count reached -1000.
  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())
{
  // Do stuff
  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) // Reached -1000 ?
  {
    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.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.