Sadece bu cevabı ekledim çünkü kabul edilen cevabın yanıltıcı olabileceğini düşünüyorum. Her durumda , kodunuzun iş parçacığı açısından güvenli olması için notify_one () 'yu bir yere çağırmadan önce muteksi kilitlemeniz gerekecektir , ancak aslında notify _ * ()' yı çağırmadan önce tekrar kilidini açabilirsiniz.
Açıklığa kavuşturmak için, wait (lk) 'ye girmeden önce kilidi ALMALISINIZ çünkü wait (), lk'nin kilidini açar ve kilit kilitli değilse Tanımsız Davranış olur. Notify_one () ile durum böyle değildir, ancak wait () girmeden ve bu çağrının muteksin kilidini açmasını sağlamadan önce notify _ * () 'ı çağırmayacağınızdan emin olmanız gerekir ; ki bu tabii ki sadece aynı muteksi sen notify _ * () çağırmadan önce kilitleyerek yapılabilir.
Örneğin, aşağıdaki durumu düşünün:
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);
}
Uyarı : Bu kod bir hata içeriyor.
Fikir şudur: evreler çiftler halinde start () ve stop () 'u çağırırlar, ancak sadece start () true döndürdüğü sürece. Örneğin:
if (start())
{
stop();
}
Bir (diğer) iş parçacığı bir noktada cancel () 'yi çağıracak ve cancel ()' den döndükten sonra 'Do stuff' için gereken nesneleri yok edecek. Ancak, start () ve stop () arasında iş parçacıkları varken cancel () geri dönmemelidir ve cancel () ilk satırını çalıştırdığında, start () her zaman false döndürür, bu nedenle yeni iş parçacıkları 'Do malzeme alanı.
Doğru çalışıyor mu?
Gerekçe şu şekildedir:
1) Herhangi bir evre, start () 'ın ilk satırını başarıyla yürütürse (ve bu nedenle true döndürürse), henüz hiçbir evre ilk cancel () satırını çalıştırmadı (toplam iş parçacığı sayısının 1000'den çok daha küçük olduğunu varsayıyoruz. yol).
2) Ayrıca, bir iş parçacığı start'ın () ilk satırını başarıyla yürütürken, ancak henüz ilk stop satırını () gerçekleştirmemişken, herhangi bir iş parçacığının cancel () 'nin ilk satırını başarıyla yürütmesi imkansızdır (yalnızca bir iş parçacığının ever çağrıları cancel ()): fetch_sub (1000) tarafından döndürülen değer 0'dan büyük olacaktır.
3) Bir iş parçacığı cancel () 'nin ilk satırını çalıştırdığında, başlangıç satırının () ilk satırı her zaman yanlış döndürür ve start ()' ı çağıran bir iş parçacığı artık 'İşleri yap' alanına girmez.
4) Başlatılacak () ve durdurulacak () çağrıların sayısı her zaman dengelidir, bu nedenle ilk iptal satırı () başarısız bir şekilde yürütüldükten sonra, her zaman bir (son) durdurma çağrısının () sayıya neden olduğu bir an olacaktır. -1000'e ulaşmak ve dolayısıyla çağrılacak bildir_bir (). Unutmayın ki, yalnızca ilk iptal satırı bu iş parçacığı ile sonuçlandığında gerçekleşebilir.
Bu kadar çok iş parçacığının start () / stop () 'u çağırdığı ve sayımın asla -1000'e ulaşmadığı ve cancel ()' nin asla geri dönmediği bir açlık probleminin yanı sıra, birinin "olası olmayan ve asla uzun sürmeyen" olarak kabul edebileceği başka bir hata daha var:
'İşleri yap' alanında bir iş parçacığı olması mümkündür, diyelim ki sadece stop () çağırıyor; o anda bir evre, fetch_sub (1000) ile 1 değerini okuyan ve içinden düşen cancel () 'nin ilk satırını yürütür. Ancak muteksi almadan ve / veya bekleme çağrısını yapmadan (lk) önce, ilk evre stop () 'un ilk satırını çalıştırır, -999 okur ve cv.notify_one ()!
Sonra bu notify_one () çağrısı, koşul değişkeninde beklemeden () ÖNCE yapılır! Ve program süresiz olarak kilitlenecektir.
Bu nedenle biz () notify_one çağırmak mümkün olmamalıdır kadar biz beklemek denir (). Bir koşul değişkeninin gücünün, muteksin atomik olarak kilidini açabilmesinde yattığını, notify_one () için bir çağrının olup olmadığını ve uykuya geçip geçmediğini kontrol ettiğini unutmayın. Bunu aptal olamaz, ancak bunu sen yanlıştan doğruya durumunu değiştirip olabilecek faktörler değişiklik yaptığınızda muteks kilitli tutma gereğini tutmak Burada anlatılan gibi çünkü yarış koşullarının notify_one () çağrısında kilitli.
Ancak bu örnekte herhangi bir koşul yoktur. Neden 'count == -1000' koşulu olarak kullanmadım? Çünkü burada bu hiç ilginç değil: -1000'e ulaşılır ulaşılmaz, 'İşler yap' alanına yeni bir iş parçacığı girmeyeceğinden eminiz. Dahası, iş parçacıkları hala start () çağırabilir ve sayımı artıracaktır (-999 ve -998'e kadar), ancak biz bunu umursamıyoruz. Önemli olan tek şey -1000'e ulaşılmış olmasıdır - böylece artık 'İşleri yap' alanında hiçbir iş parçacığı olmadığından emin olabiliriz. Notify_one () çağrılırken durumun bu olduğundan eminiz, ancak cancel () muteksini kilitlemeden önce notify_one () 'yu çağırmadığımızdan nasıl emin olabiliriz? Sadece notify_one () 'dan kısa süre önce cancel_mutex'i kilitlemek elbette yardımcı olmayacak.
Sorun, bir durum için bekliyor değiliz buna rağmen hala orada, yani olan bir durumdur ve biz mutex'i kilitlemek gerekiyor
1) bu koşula ulaşılmadan önce 2) notify_one çağrılmadan önce.
Dolayısıyla doğru kod şu olur:
void stop()
{
if (count.fetch_sub(1) == -999)
{
cancel_mutex.lock();
cancel_mutex.unlock();
cv.notify_one();
}
}
[... aynı başlangıç () ...]
void cancel()
{
std::unique_lock<std::mutex> lk(cancel_mutex);
if (count.fetch_sub(1000) == 0)
return;
cancel_cv.wait(lk);
}
Tabii ki bu sadece bir örnek ama diğer durumlar birbirine çok benziyor; Eğer bir koşullu değişken kullanın hemen her durumda ihtiyaç olduğunu muteksi olması) notify_one (çağırmadan önce (kısaca) kilitli, aksi takdirde sen beklemek çağırmadan önce () diyoruz olması mümkündür.
Bu durumda notify_one () 'yu çağırmadan önce muteksin kilidini açtığıma dikkat edin, çünkü aksi takdirde notify_one () çağrısının koşul değişkenini bekleyen iş parçacığını uyandırması (küçük) şansı vardır ve bu daha sonra muteksi almaya çalışır ve Mutex'i tekrar serbest bırakmadan önce blok. Bu, gerekenden biraz daha yavaş.
Bu örnek, koşulu değiştiren satır wait () 'i çağıran aynı evre tarafından yürütüldüğü için biraz özeldi.
Daha olağan olan durum, bir iş parçacığının bir koşulun gerçek olmasını beklediği ve başka bir iş parçacığının bu koşulda yer alan değişkenleri değiştirmeden önce kilidi aldığı (muhtemelen doğru olmasına neden olduğu) durumdur. Bu durumda muteks , koşul gerçekleşmeden hemen önce (ve sonra) kilitlenir - bu nedenle, bu durumda notify _ * () 'yı çağırmadan önce muteksin kilidini açmak tamamen uygundur.
wait morphing
optimizasyonu etkinleştirmek için ) Bu bağlantıda açıklanan temel kural: Daha öngörülebilir sonuçlar için 2'den fazla iş parçacığı olan durumlarda İLE kilitle bildirmek daha iyidir.