list :: empty () çok iş parçacıklı davranış?


9

Öğeleri kapmak için farklı konuların olmasını istediğim bir liste var. Muteksin liste boşken kilitlenmesini önlemek için kilitlemeden empty()önce kontrol ederim .

Çağrı list::empty()% 100 doğru değilse sorun değil. Sadece eşzamanlı list::push()ve list::pop()aramaların çökmesini veya bozulmasını önlemek istiyorum .

VC ++ ve Gnu GCC'nin sadece bazen empty()yanlış olacağını ve daha kötü bir şey olacağını varsayabilir miyim ?

if(list.empty() == false){ // unprotected by mutex, okay if incorrect sometimes
    mutex.lock();
    if(list.empty() == false){ // check again while locked to be certain
         element = list.back();
         list.pop_back();
    }
    mutex.unlock();
}

1
Hayır, bunu kabul edemezsiniz. Sen VC gibi bir eşzamanlı kabı kullanabilirsiniz concurrent_queue
Panagiotis Kanavos

2
@Fureeish Bu bir cevap olmalı. Ben std::list::sizetemelde boyutu (düğüm sayısı) ayrı bir değişken içinde depolanması gerektiği anlamına gelir sabit zaman karmaşıklığı garanti ekledi; diyelim size_. std::list::emptymuhtemelen bir şey döndürür size_ == 0ve eşzamanlı okuma ve yazma size_veri yarışına, dolayısıyla UB'ye neden olur.
Daniel Langr

@DanielLangr "Sabit zaman" nasıl ölçülür? Tek bir işlev çağrısında mı yoksa programın tamamında mı?
curiousguy

1
@curiousguy: DanielLangr sorunuzu "liste düğümlerinin sayısından bağımsız" olarak cevapladı, yani O (1) 'in tam tanımı, her elemanın sayısına bakılmaksızın sabit bir süreden daha kısa sürede yapıldığı anlamına gelir. en.wikipedia.org/wiki/Big_O_notation#Orders_of_common_functions Diğer seçenek (C ++ 11'e kadar) doğrusal olacaktır = O (n) anlamı, bu boyutun öğeleri (bağlantılı liste) sayması gerekir, hangisi daha da kötü olur eşzamanlılık (sayaçtaki atomik olmayan okuma / yazma işleminden daha belirgin veri yarışı).
firda

1
@curiousguy: dV ile kendi örneğinizi alarak, zaman karmaşıklığı aynı matematik sınırlarıdır. Bütün bunlar ya özyinelemeli olarak tanımlanır, ya da "Her N için f (N) <C olacak şekilde C vardır" şeklinde, yani O (1) tanımıdır (verilen / her HW için sabit C vardır algo herhangi bir girdide C-zamanından daha kısa sürede biter). İtfa edilmiş ortalamalar , yani bazı girdilerin işlenmesinin daha uzun sürebileceği anlamına gelir (örneğin yeniden hash / yeniden ayırma gerekir), ancak yine de ortalama olarak sabittir (tüm olası girişleri varsayarak).
firda

Yanıtlar:


10

Çağrı list::empty()% 100 doğru değilse sorun değil.

Hayır, sorun değil. Listenin bazı senkronizasyon mekanizmalarının (boşluğu kilitleme) dışında boş olup olmadığını kontrol ederseniz veri yarışınız olur. Bir veri yarışına sahip olmak, tanımlanmamış davranışınız olduğu anlamına gelir. Tanımlanmamış davranışa sahip olmak, artık program hakkında bir neden bulamayacağımız anlamına gelir ve aldığınız çıktılar "doğru" olur.

Akıl sağlığınıza değer verirseniz, kontrol etmeden önce performans isabetini alır ve muteksi kilitlersiniz. Bununla birlikte, liste sizin için doğru kap bile olmayabilir. Onunla ne yaptığınızı tam olarak bize bildirirseniz, daha iyi bir kap önerebiliriz.


Kişisel bakış açısı, çağrı list::empty()hiçbir ilgisi olmayan bir okuma eylemidirrace-condition
Ngọc Khánh Nguyễn

3
@ NgọcKhánhNguyễn Listeye eleman ekliyorlarsa, aynı zamanda boyutu yazarken ve okurken kesinlikle bir veri yarışına neden olur.
NathanOliver

6
@ NgọcKhánhNguyễn Bu yanlış. Bir yarış koşulu read-writeveya write-write. Bana inanmıyorsanız , veri yarışları ile ilgili standart bölümü bir okumaya verin
NathanOliver

1
@ NgọcKhánhNguyễn: Ne yazma ne de okumanın atomik olması garanti edilmediğinden, aynı anda yürütülebilir, bu nedenle okuma tamamen yanlış bir şey alabilir (yırtık okuma olarak adlandırılır). Küçük endian 8-bit MCU'da 0x00FF değerini 0x0100 olarak değiştirdiğini düşünün, düşük 0xFF değerini 0x00'e yeniden yazarak okuyun ve okuma her iki baytı okuyarak (yazma iş parçacığı yavaşladı veya askıya alındı) yazmaya devam ediyor, yüksek baytı 0x01'e güncelleyerek devam ediyor, ancak okuma iş parçacığının değeri zaten yanlış (ne 0x00FF, ne 0x0100 ama beklenmeyen 0x0000).
firda

1
@ NgọcKhánhNguyễn Bazı mimarilerde olabilir ancak C ++ sanal makinesi böyle bir garanti vermez. Donanımınız olsa bile, derleyicinin kodu hiçbir zaman bir değişiklik görmeyeceğiniz şekilde optimize etmesi yasal olacaktır, çünkü iş parçacığı senkronizasyonu yoksa, yalnızca tek bir iş parçacığı çalıştırdığını varsayabilir ve buna göre optimize edebilir.
NathanOliver

6

Birbirleri ile eşitlenmemiş bir okuma ve yazma (büyük olasılıkla sizeüyesine, std::listböyle adlandırıldığını varsayarsak) vardır . Bir iş parçacığının iç tarafa girip yürütürken bir iş parçacığının (dışınızda ) aradığını düşünün . Daha sonra, muhtemelen değiştirilen bir değişkeni okuyorsunuz. Bu tanımsız bir davranıştır.empty()if()if()pop_back()


2

İşlerin nasıl yanlış gidebileceğine bir örnek olarak:

Yeterince akıllı bir derleyici mutex.lock(), list.empty()dönüş değerini muhtemelen değiştiremediğini ve böylece iç ifkontrolü tamamen atlayamayacağını görebiliyordu , sonunda pop_backbirinciden sonra son öğesi kaldırılmış bir listeye yol açtı if.

Bunu neden yapabilir? Eşzamanlama yoktur list.empty(), bu nedenle eşzamanlı olarak değiştirilirse, bu bir veri yarışı oluşturur. Standart, programların veri yarışlarına sahip olmayacağını, bu nedenle derleyicinin bunu kabul edeceğini söylüyor (aksi takdirde neredeyse hiçbir optimizasyon yapamaz). Bu nedenle, senkronize edilmemiş olan tek iş parçacıklı bir perspektif varsayabilir list.empty()ve sabit kalması gerektiği sonucuna varabilir.

Bu, kodunuzu kırabilecek çeşitli optimizasyonlardan (veya donanım davranışlarından) yalnızca biridir.


Mevcut derleyiciler optimize etmek bile istemiyor a.load()+a.load()...
curiousguy

1
@curiousguy Bunun nasıl optimize edilmesini istersiniz? Orada tam sıralı tutarlılık istersiniz, böylece elde edersiniz ...
Max Langhof

@MaxLanghof Optimizasyonun a.load()*2açık olduğunu düşünmüyor musunuz? Hatta değil a.load(rel)+b.load(rel)-a.load(rel)zaten optimize edilmiştir. Hiçbir şey. Neden kilitlerin (özünde çoğunlukla seq tutarlılığı vardır) daha iyi hale getirilmesini bekliyorsunuz?
curiousguy

@curiousguy Atomik olmayan erişimlerin (burada kilitten önce ve sonra) ve atomiklerin bellek sıralaması tamamen farklı olduğu için mi? Kilidin "daha fazla" optimize edilmesini beklemiyorum, senkronize olmayan erişimlerin ardışık olarak tutarlı erişimlerden daha fazla optimize edilmesini bekliyorum. Kilidin varlığı benim açımdan anlamsız. Ve hayır, derleyicinin optimizasyon a.load() + a.load()yapmasına izin verilmiyor 2 * a.load(). Daha fazla bilgi edinmek istiyorsanız, bu konuda bir soru sormaktan çekinmeyin.
Max Langhof

@ MaxLanghof Ne söylemeye çalıştığın hakkında hiçbir fikrim yok. Kilitler esasen ardışık olarak tutarlıdır. Uygulama neden bazılarını değil (bazılarını) bazı diş açma ilkellerinde (kilitlerinde) optimizasyon yapmaya çalışsın? Atom dışı erişimin atom kullanımı etrafında optimize edilmesini mi bekliyorsunuz?
curiousguy
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.