Çeşitli çevrimiçi kaynaklardan (örn. Bu ve bu ), C ++ 11 Standardından ve burada verilen cevaplardan geçtikten sonra bunu kendim cevaplamaya çalışıyorum .
İlgili sorular birleştirilir (örneğin, " neden! Bekleniyor? ", "Neden Compar_exchange_weak () döngüye koyulsun? " İle birleştirilir ve cevaplar buna göre verilir.
Compar_exchange_weak () neden neredeyse tüm kullanımlarda bir döngü içinde olmak zorunda?
Tipik Desen A
Atomik değişkendeki değere göre bir atomik güncellemeye ihtiyacınız var. Bir hata, değişkenin istediğimiz değerle güncellenmediğini ve onu yeniden denemek istediğimizi gösterir. Eşzamanlı yazma veya sahte başarısızlık nedeniyle başarısız olup olmadığını gerçekten umursamadığımızı unutmayın . Ama bunu bakım yapmak bizi olduğu bu değişikliği yapmak.
expected = current.load();
do desired = function(expected);
while (!current.compare_exchange_weak(expected, desired));
Gerçek dünya örneği, birkaç iş parçacığının aynı anda tekil bağlantılı bir listeye bir öğe eklemesidir. Her iş parçacığı ilk önce baş işaretçisini yükler, yeni bir düğüm atar ve başlığı bu yeni düğüme ekler. Son olarak, yeni düğümü kafa ile değiştirmeye çalışır.
Başka bir örnek, mutex'i std::atomic<bool>
. En çok bir evre ilk set hangi iplik bağlı bir anda kritik bölüm girebilirsiniz current
için true
ve döngü çıkın.
Tipik Desen B
Bu aslında Anthony'nin kitabında bahsedilen modeldir. A modelinin aksine , atom değişkeninin bir kez güncellenmesini istersiniz, ancak bunu kimin yaptığı umurunuzda değildir. Güncellenmediği sürece tekrar deneyin. Bu genellikle boole değişkenleriyle kullanılır. Örneğin, bir durum makinesinin ilerlemesi için bir tetikleyici uygulamanız gerekir. Hangi iplik tetiği çektiğine bakılmaksızın.
expected = false;
while (!current.compare_exchange_weak(expected, true) && !expected);
Bir muteksi uygulamak için genellikle bu kalıbı kullanamayacağımızı unutmayın. Aksi takdirde, birden fazla iş parçacığı aynı anda kritik bölümün içinde olabilir.
Bununla birlikte, compare_exchange_weak()
bir döngünün dışında kullanılması nadirdir . Aksine, güçlü versiyonun kullanımda olduğu durumlar vardır. Örneğin,
bool criticalSection_tryEnter(lock)
{
bool flag = false;
return lock.compare_exchange_strong(flag, true);
}
compare_exchange_weak
burada uygun değildir çünkü sahte başarısızlık nedeniyle geri döndüğünde, muhtemelen kritik bölümü henüz kimse işgal etmemiştir.
Konu Açlıktan mı?
Bahsetmeye değer bir nokta, sahte başarısızlıklar olmaya devam ederse ve böylece iş parçacığı aç bırakılırsa ne olacağıdır. Teorik compare_exchange_XXX()
olarak, bir dizi talimat olarak uygulandığında platformlarda gerçekleşebilir (örneğin, LL / SC). LL ve SC arasındaki aynı önbellek hattına sık erişim, sürekli sahte hatalara neden olacaktır. Daha gerçekçi bir örnek, tüm eşzamanlı iş parçacıklarının aşağıdaki şekilde araya eklendiği aptal bir zamanlamadan kaynaklanmaktadır.
Time
| thread 1 (LL)
| thread 2 (LL)
| thread 1 (compare, SC), fails spuriously due to thread 2's LL
| thread 1 (LL)
| thread 2 (compare, SC), fails spuriously due to thread 1's LL
| thread 2 (LL)
v ..
Olabilir mi?
Neyse ki, C ++ 11'in gerektirdiği sayesinde sonsuza kadar olmayacak:
Uygulamalar, zayıf karşılaştırma ve değiştirme işlemlerinin, atom nesnesi beklenenden farklı bir değere sahip olmadığı veya atom nesnesinde eşzamanlı değişiklikler olmadığı sürece tutarlı bir şekilde yanlış döndürmemesini sağlamalıdır.
Neden Compare_exchange_weak () kullanıp döngüyü kendimiz yazıyoruz? Karşılaştırma_exchange_strong () kullanabiliriz.
Değişir.
Durum 1: Her ikisinin de bir döngü içinde kullanılması gerektiğinde. C ++ 11 diyor ki:
Karşılaştırma ve değiştirme bir döngü içindeyken, zayıf sürüm bazı platformlarda daha iyi performans sağlar.
X86'da (en azından şu anda. Belki bir gün daha fazla çekirdek tanıtıldığında performans için LL / SC gibi benzer bir şemaya başvurur), zayıf ve güçlü sürüm esasen aynıdır çünkü ikisi de tek bir talimata indirgenir cmpxchg
. Atomik olarakcompare_exchange_XXX()
uygulanmayan diğer bazı platformlarda (burada tek bir donanım ilkelinin olmadığı anlamına gelir), güçlü olanın sahte arızaları ele alması ve buna göre yeniden denemesi gerekeceğinden, döngü içindeki zayıf sürüm savaşı kazanabilir.
Fakat,
Nadiren, biz tercih edebilirsiniz compare_exchange_strong()
üzerinde compare_exchange_weak()
bile bir döngü içinde. Örneğin, atomik değişken yüklenir ve hesaplanan yeni bir değer değiştirilir (yukarıya bakın function()
) arasında yapılacak çok şey olduğunda . Atomik değişkenin kendisi sık sık değişmiyorsa, her sahte başarısızlık için maliyetli hesaplamayı tekrar etmemize gerek yoktur. Bunun yerine, bu compare_exchange_strong()
tür hataları "absorbe etmeyi " umabiliriz ve hesaplamayı yalnızca gerçek bir değer değişikliği nedeniyle başarısız olduğunda tekrar ederiz.
Durum 2: Yalnızca compare_exchange_weak()
bir döngü içinde kullanılması gerektiğinde. C ++ 11 ayrıca şunu da söylüyor:
Zayıf bir karşılaştırma ve değiştirme bir döngü gerektirdiğinde ve güçlü bir döngü gerektirmediğinde, güçlü olan tercih edilir.
Bu genellikle, zayıf sürümden kaynaklanan sahte hataları ortadan kaldırmak için döngü oluşturduğunuzda geçerlidir. Eşzamanlı yazma nedeniyle değişim başarılı veya başarısız olana kadar yeniden denersiniz.
expected = false;
while (!current.compare_exchange_weak(expected, true) && !expected);
En iyi ihtimalle, tekerlekleri yeniden icat etmek ve aynı performansı sağlamaktır compare_exchange_strong()
. Daha da kötüsü? Bu yaklaşım, donanımda sahte olmayan karşılaştırma ve değiştirme sağlayan makinelerden tam olarak yararlanmada başarısız olur .
Son olarak, başka şeyler için döngü yaparsanız (örneğin, yukarıdaki "Tipik A Örneği" ne bakın), o zaman compare_exchange_strong()
bizi önceki duruma geri götüren bir döngüye de konulacak iyi bir şans vardır .