Soru C # 'daki basit bir boole bayrağına erişirken uçucu olarak kilitlenmeli veya işaretlenmeli mi?


Sadece arka plan iş parçacığı üzerinde çalışan basit bir işleminiz olduğunu söyleyelim. Bu işlemi iptal etmek için bir yol belirtmek istersiniz, böylece bir iptal düğmesinin tıklama olay işleyicisinden true olarak ayarladığınız bir boole bayrağı oluşturun.

private bool _cancelled;

private void CancelButton_Click(Object sender ClickEventArgs e)
{
    _cancelled = true;
}

Şimdi, GUI dizisinden iptal bayrağını ayarlıyorsunuz, ancak arka plan dizisinden okuyorsunuz. Boole erişmeden önce kilitlemeniz gerekiyor mu?

Bunu yapman gerekecek (ve ayrıca düğme tıklama olay işleyicisinde de kilitlenecek):

while(operationNotComplete)
{
    // Do complex operation

    lock(_lockObject)
    {
        if(_cancelled)
        {
            break;
        }
    }
}

Veya bunu yapmak için kabul edilebilir (kilitsiz):

while(!_cancelled & operationNotComplete)
{
    // Do complex operation
}

Ya da _cancelled değişkenini uçucu olarak işaretleme. Bu gerekli mi?

[Orada Dahili CancelAsync () yöntemi ile BackgroundWorker sınıfı var biliyorum, ama ben semantik ve kilitleme ve dişli değişken erişim kullanımı ile ilgileniyorum, özel uygulama değil, kod sadece bir örnektir.]

İki teori var gibi görünüyor.

1) Basit bir dahili tip olduğundan (ve dahili türlere erişim .net içinde atomiktir) ve sadece bir yerde yazdığımızdan ve sadece arka plan ipliği üzerinde okuduğumuzdan dolayı, kilitleme veya uçucu olarak işaretlemeye gerek yoktur.
2) Eğer onu derleyici olarak işaretlemelisiniz, çünkü eğer derleyici, while döngüsündeki okumayı en iyi duruma getirebilir, çünkü değeri değiştirebileceği hiçbir şey düşünmez.

Doğru teknik hangisi? (Ve neden?)

[Düzenle: Bu konuda açıkça tanımlanmış iki muhalif okul var gibi gözüküyor. Bu konuda kesin bir cevap arıyorum, bu yüzden lütfen mümkünse nedenlerinizi yazın ve kaynaklarınızı cevabınızla birlikte belirtin.]


40
2017-08-03 12:50


Menşei


Evet sen yap ya ihtiyacım var volatile veya lock (hafıza engeli olarak hareket etmek): bkz. stackoverflow.com/questions/458173/... - Marc Gravell♦
Bu, Simon'un EFraim ile yaşadığı kitlesel tartışmalarda kırmızıyla yüzleşeceği anlamına geliyor mu?stackoverflow.com/questions/1221839/...) - ThePower
@Marc: Güzel örnek, teşekkürler. @ThePower: Emin olmadığım zaman ellerimi koymaktan mutluluk duyuyorum, umarım sadece yumuşak pembe yüzlerden kaçacağım :) - Simon P Stevens
Joe Duffy'nin "Windows Üzerindeki Eşzamanlı Programlaması" kitap listeme ekleniyor! - Mitch Wheat
@İmon, saygı - ThePower


Cevaplar:


İlk olarak, iş parçacığı zor;

Evet, aksine tüm söylentilere rağmen, olduğu için gerekli ya kullanım lock  veya  volatile (ancak her ikisi de değil) bir bool Birden çok konudan.

Basit tipler ve çıkış bayrağı gibi erişim içinbool), sonra volatile yeterlidir - bu, iş parçacıklarının yazmaçlarındaki değeri önbelleğe almamasını sağlar (yani: iş parçacıklarından biri hiçbir zaman güncelleştirmeyi görmez).

Daha büyük değerler (atomikliğin bir sorun olduğu) veya senkronize etmek istediğiniz bir yer için sıra işlemlerin ("var olmayan ve ekleme" sözlük erişimi) tipik bir örneği, lock daha çok yönlüdür. Bu bir bellek bariyeri olarak hareket eder, bu yüzden hala iplik güvenliğini sağlar, ancak nabız / bekletme gibi diğer özellikleri sağlar. Kullanmamanız gerektiğini unutmayın. lock bir değer türü veya string; ne de Type veya this; En iyi seçenek alan olarak kendi kilitleme nesnesine sahip olmaktır (readonly object syncLock = new object();) ve bunu kilitle.

Eşitleme yapmazsanız ne kadar kötü bir şekilde kırılacağını (yani sonsuza dek döngü) gösteren bir örnek için buraya bakın.

Birden çok programa yaymak için, bir Mutex veya *ResetEvent Ayrıca yararlı olabilir, ancak bu tek bir exe için fazladır.


32
2017-08-03 13:22



Bence bu meseleyi hallediyor. Kilit veya uçucu kullanmazsanız, nasıl yanlış gidebileceğini gösteren orada tekrarlanabilir bir örnek. - Simon P Stevens
@MarcGravell 2 iş parçacığının aynı anda değişken değişkene yazdığı herhangi bir şans var mı? - onmyway133
@entropy evet, bunu durdurmak için hiçbir şey yok. Değerlerden biri kazanacak; hangisi tanımlanmamıştır. Derleyicinin yalnızca kullanmanıza izin verdiğini unutmayın. volatile atomik olarak yazılabilen tiplerle, bu yüzden bir parçalanmış değerle sonuçlanmayacaksınız - Marc Gravell♦


_cancelled olmalıdır volatile. (eğer kilitlemeyi seçmezsen)

Bir iş parçacığı değeri değiştirirse _cancelled, diğer konular güncellenmiş sonucu göremeyebilir.

Ayrıca, okuma / yazma işlemlerini düşünüyorum _cancelled Hangi atomik:

CLI spesifikasyonunun 12.6.6 bölümü şunları belirtmektedir:   "Uygun bir CLI,   Düzgün okuma ve yazma erişimi   daha büyük boyutlu bellek konumları   yerli kelime boyutundan daha atomiktir   tüm yazma bir   konum aynı boyutta. "


6
2017-08-03 12:59



@Mitch: Fakat bu durumda bu, işleri halledebileceği anlamına geliyor. - EFraim
Bir düzeltme noktası olarak, kilitleme, bir değişkene okuma veya yazma önbelleğe alınıp alınmadığı üzerinde hiçbir etkiye sahip olmayacaktır. Kilitleme, değişken bir değişkenin yerine geçmez. - Adam Robinson
Atomizmin onunla çok az ilgisi vardır ... eğer diğer iplik hala kendi yazmaçlarında çiğniyorsa o zaman değeri 1 yığına veya 26'ya yazdığınızda sıfır fark yaratır. - Marc Gravell♦
@Adam - kilit, aynı amaca hizmet eden bir bellek bariyeri olarak görür. - Marc Gravell♦
Lock () işlevinin kullanılması, alanın geçici olmasını sağlamak için aynı garantileri verir. - Daniel Brückner


Tek bir senaryo senaryosu ve bir boole alanınız devleti bozma riski olmayan basit bir yapıya sahip olduğunuz için kilitleme gerekli değildir.ne yanlış ne de doğru olan bir boole değeri elde etmek mümkünken). Ancak alanı şu şekilde işaretlemelisiniz volatile Derleyicinin bazı optimizasyonlar yapmasını engellemek için. Olmadan volatile değiştirici, derleyicinin, iş parçacığınız üzerindeki döngünüzün yürütülmesi sırasında bir kayıttaki değeri önbelleğe alabildiği gibi, döngü de değişmiş değeri asla tanımayacaktır. Bu MSDN makalesiNasıl Yapılır: Konuları Oluşturma ve Sonlandırma (C # Programlama Kılavuzu)) bu sorunu ele almaktadır. Kilitlemeye ihtiyaç varken, bir kilit alanın işaretlenmesiyle aynı etkiye sahip olacaktır. volatile.


5
2017-08-03 13:07



Doğru. Bu bayrağın değerini okuduğunuzdan veya ayarladığınızdan, tek ihtiyacınız olan şey volatile bağlıyor. - jpbochi
Ancak, teoride boolean alanının güncellemesi atomik değilse ve kısmi bir güncelleme doğru veya yanlış olmayan bir değerle sonuçlanırsa, oldukça kullanışsız bir senaryoya sahip olabilirsiniz, ancak bu, verilen kullanım senaryosunda sorun yaratmayacaktır. - Daniel Brückner
@Daniel - bir bool güncellemesi spec tarafından atomik olması garantilidir. - Marc Gravell♦
ECMA334v4 §12.5 Değişken referansların atomikliği Aşağıdaki veri tiplerinin okuma ve yazma işlemleri atomik olmalıdır: bool, char, byte, sbyte, kısa, ushort, uint, int, float ve referans türleri. Ayrıca, önceki listede yer alan bir türdeki enum türlerini okur ve yazarlar da atomik olacaktır. - Marc Gravell♦
Bunun için teşekkürler. Sadece RFC 2119'a bir göz attım ve "yapmalı" gibi "eğilimli" yorumlama eğiliminde olduğumu fark ettim, çünkü Almanca "kelimesi" kelimesine benziyordu. - Daniel Brückner


İş parçacığı senkronizasyonu için, aşağıdakilerden birini kullanmanız önerilir. EventWaitHandle gibi sınıflar ManualResetEvent. Burada olduğu gibi basit bir boole bayrağı kullanmak marjinal olarak daha basitken (ve evet, bunu volatile), IMO threading araçlarını kullanma pratiğine girmek daha iyidir. Amaçlarınız için böyle bir şey yaparsınız ...

private System.Threading.ManualResetEvent threadStop;

void StartThread()
{
    // do your setup

    // instantiate it unset
    threadStop = new System.Threading.ManualResetEvent(false); 

    // start the thread
}

İpinde ..

while(!threadStop.WaitOne(0) && !operationComplete)
{
    // work
}

Sonra GUI'de iptal etmek ...

threadStop.Set();

2
2017-08-03 13:01



Bir OS ilkel, genellikle tek bir uygulama IMO için fazladır. Çoğu şey sadece bir Monitor. - Marc Gravell♦
@Marc: Burada bir * ResetEvent açık bir şekilde gerekli olmasa da, tek bir uygulama için çok fazla olduğunu söylemeye hazır olduğumu düşünmüyorum. Bir uygulamada birden fazla iş parçacığının, ManualResetEvent gibi bir EventWaitHandle işlevinin kullanılmasını gerektirebilecek birçok örneği vardır. - Adam Robinson


Yukarı Bak Interlocked.Exchange (). Karşılaştırma için kullanılabilecek yerel bir değişkene çok hızlı bir kopyalama yapar. Lock () 'dan daha hızlıdır.


1
2017-08-03 13:05



Hız faktörleri değil; doğruluk ... - Mitch Wheat
+1, her zaman en iyisi değil, üç (kilit / kilitlenmiş / uçucu) olasılıklardan biridir. - Henk Holterman
Interlocked.Exchange (), bir döngüyü kontrol etmek için kullanılan bir değişken için ilk tercihim olmayacaktır. Bir Event / WaitHandle kullanırdım. Tek bir tam sayı / boole değişkeni atomik olarak almanız gerekiyorsa, bu iyi bir seçimdir. - hughdbrown