referans ataması atomiktir, öyleyse neden Interlocked.Exchange (ref Object, Object) gerekli?


108

Çok iş parçacıklı asmx web hizmetimde, kendi türünde SystemData olan ve birkaçtan oluşan List<T>ve olarak Dictionary<T>işaretlenmiş bir sınıf alanı _allData vardı volatile. Sistem verisi ( _allData) arada bir yenileniyor ve bunu adında başka bir nesne oluşturarak newDatave veri yapılarını yeni verilerle doldurarak yapıyorum . Bittiğinde sadece atarım

private static volatile SystemData _allData

public static bool LoadAllSystemData()
{
    SystemData newData = new SystemData();
    /* fill newData with up-to-date data*/
     ...
    _allData = newData.
} 

Bu, atama atomik olduğundan ve eski verilere referans veren iş parçacıkları onu kullanmaya devam ettiğinden ve geri kalanı atamadan hemen sonra yeni sistem verilerine sahip olduğundan çalışmalıdır. Ancak meslektaşım, volatileanahtar kelime ve basit InterLocked.Exchangeatama kullanmak yerine kullanmam gerektiğini çünkü bazı platformlarda referans atamanın atomik olmasının garanti edilmediğini söyledi. Üstelik: ne zaman beyan the _allDatagibi sahadanvolatile

Interlocked.Exchange<SystemData>(ref _allData, newData); 

uyarı verir "uçucu bir alana yapılan bir referans geçici olarak değerlendirilmeyecektir" Bu konuda ne düşünmeliyim?

Yanıtlar:


180

Burada çok sayıda soru var. Onları teker teker ele alarak:

referans ataması atomiktir, öyleyse neden Interlocked.Exchange (ref Object, Object) gerekli?

Referans ataması atomiktir. Interlocked.Exchange sadece referans ataması yapmaz. Bir değişkenin mevcut değerini okur, eski değeri saklar ve yeni değeri değişkene atomik bir işlem olarak atar.

meslektaşım, bazı platformlarda referans atamasının atomik olduğunun garanti edilmediğini söyledi. Meslektaşım doğru muydu?

Hayır. Referans atamasının tüm .NET platformlarında atomik olacağı garanti edilir.

Meslektaşım yanlış önermelere dayanarak muhakeme ediyor. Bu, sonuçlarının yanlış olduğu anlamına mı geliyor?

Şart değil. Meslektaşınız size kötü nedenlerle iyi tavsiyeler veriyor olabilir. Belki de Interlocked.Exchange'i kullanmanız için başka bir neden daha vardır. Kilitsiz programlama delicesine zordur ve alandaki uzmanlar tarafından benimsenen köklü uygulamalardan ayrıldığınız anda, yabani otlara gömülür ve en kötü türden yarış koşullarını riske atarsınız. Ben ne bu alanda uzmanım ne de kodunuz konusunda uzmanım, bu yüzden öyle ya da böyle bir yargıya varamam.

uyarı verir "uçucu bir alana yapılan bir referans geçici olarak değerlendirilmeyecektir" Bu konuda ne düşünmeliyim?

Bunun genel olarak neden bir sorun olduğunu anlamalısınız. Bu, bu özel durumda uyarının neden önemsiz olduğunun anlaşılmasına yol açacaktır.

Derleyicinin bu uyarıyı vermesinin nedeni, bir alanı geçici olarak işaretlemenin "bu alanın birden çok iş parçacığında güncelleneceği anlamına gelmesidir - bu alanın değerlerini önbelleğe alan herhangi bir kod oluşturmayın ve herhangi bir okuma veya yazma işleminin yapıldığından emin olun. bu alan, işlemci önbelleği tutarsızlıkları nedeniyle "zamanda ileri ve geri taşınmaz". "

(Her şeyi zaten anladığınızı varsayıyorum. Eğer uçucunun anlamını ve işlemci önbelleği anlamını nasıl etkilediğini ayrıntılı olarak anlamadıysanız, nasıl çalıştığını anlamıyorsunuz ve geçici kullanmamalısınız. Kilitsiz programlar doğru yapmak çok zordur; programınızın doğru olduğundan emin olun çünkü nasıl çalıştığını anlıyorsunuz, tesadüfen değil.)

Şimdi, geçici bir alanın takma adı olan bir değişkeni o alana bir ref geçirerek yaptığınızı varsayalım. Çağrılan yöntemin içinde, derleyicinin referansın geçici anlambilimlere sahip olması gerektiğini bilmesi için hiçbir nedeni yoktur! Derleyici neşeyle uçucu alanlar için kuralları uygulamak başarısız yöntemi için kod oluşturur, fakat değişken olan uçucu bir alan. Bu, kilitsiz mantığınızı tamamen mahvedebilir; varsayım her zaman uçucu bir alana her zaman geçici anlambilimle erişildiği şeklindedir. Onu bazen uçucu, bazen de uçucu olarak ele almanın hiçbir anlamı yoktur; her zaman tutarlı olmanız gerekir, aksi takdirde diğer erişimlerde tutarlılığı garanti edemezsiniz.

Bu nedenle, derleyici bunu yaptığınızda uyarır, çünkü muhtemelen dikkatlice geliştirdiğiniz kilitsiz mantığınızı tamamen bozacaktır.

Tabii ki, Interlocked.Exchange olan uçucu bir alan elde etmek için yazılan ve doğru olanı yap. Bu nedenle uyarı yanıltıcıdır. Buna çok pişmanım; Yapmamız gereken şey, Interlocked.Exchange gibi bir yöntemin yazarının yönteme "ref alan bu yöntem değişken üzerinde uçucu semantiği zorlar, bu nedenle uyarıyı bastırın" diyerek bir öznitelik koyabileceği bir mekanizma uygulamaktır. Belki de derleyicinin gelecekteki bir sürümünde bunu yapacağız.


1
Interlocked.Exchange duyduğuma göre bir bellek bariyerinin oluşturulduğunu da garanti ediyor. Örneğin, yeni bir nesne yaratırsanız, daha sonra birkaç özellik atarsanız ve ardından nesneyi Interlocked.Exchange kullanmadan başka bir referansta saklarsanız, derleyici bu işlemlerin sırasını bozabilir ve böylece ikinci referansa erişimin iş parçacığı değil kasa. Gerçekten öyle mi? Interlocked.Exchange kullanmak mantıklı mı? Bu tür senaryolar mı?
Mike

12
@Mike: Düşük kilitli çok iş parçacıklı durumlarda muhtemelen gözlemlenenlere gelince, bir sonraki kişi kadar cahilim. Cevap muhtemelen işlemciden işlemciye değişecektir. Sorunuzu bir uzmana yöneltmeli veya ilginizi çekiyorsa konuyu okumalısınız. Joe Duffy'nin kitabı ve blogu başlamak için iyi yerlerdir. Benim kuralım: çoklu okuma kullanma. Gerekirse, değişmez veri yapıları kullanın. Yapamıyorsanız, kilit kullanın. Yalnızca kilitleri olmayan değişken verilere sahip olmanız gerektiğinde , düşük kilit tekniklerini düşünmelisiniz.
Eric Lippert

Cevabın için teşekkürler Eric. Gerçekten ilgimi çekiyor, bu yüzden çoklu okuma ve kilitleme stratejileri hakkında kitaplar ve bloglar okuyorum ve bunları kodumda uygulamaya çalışıyorum. Ama öğrenecek çok şey var ...
Mike

2
@EricLippert "Çok iş parçacıklı kullanma" ve "Gerekirse, değişmez veri yapıları kullanın" arasında, orta ve çok yaygın olan "bir çocuk iş parçacığına sahip olmak sadece özel olarak sahip olunan giriş nesnelerini kullanır ve ana iş parçacığı sonuçları tüketir sadece çocuk bitirdiğinde. " Olduğu gibi var myresult = await Task.Factory.CreateNew(() => MyWork(exclusivelyLocalStuffOrValueTypeOrCopy));.
John

1
@John: Bu iyi bir fikir. İş parçacıkları ucuz süreçler gibi ele almaya çalışıyorum: onlar bir iş yapmak ve bir sonuç üretmek için oradalar, ana programın veri yapıları içinde ikinci bir kontrol zinciri olarak koşturmak için değil. Ama iş parçacığının yaptığı iş miktarı o kadar büyükse, ona bir süreç gibi davranmak mantıklıysa, o zaman sadece bir süreç yap derim!
Eric Lippert

9

Meslektaşınız yanılıyor ya da C # dil belirtiminde olmayan bir şey biliyor.

5.5 Değişken referansların atomikliği :

"Aşağıdaki veri türlerinin okuma ve yazma işlemleri atomiktir: bool, char, bayt, sbyte, short, ushort, uint, int, float ve başvuru türleri."

Böylece, bozuk bir değer alma riski olmadan uçucu referansa yazabilirsiniz.

Elbette, bir seferde birden fazla iş parçacığının bunu yapması riskini en aza indirmek için, yeni verileri hangi iş parçacığının getirmesi gerektiğine nasıl karar verdiğinize dikkat etmelisiniz.


3
@guffa: evet onu da okudum. bu, orijinal soruyu "referans ataması atomiktir, öyleyse neden Interlocked.Exchange (ref Nesne, Nesne) gerekli?" cevapsız
char m

@zebrabox: Ne demek istiyorsun? değillerse? sen ne yapardın?
char m

@matti: Atomik bir işlem olarak bir değer okumanız ve yazmanız gerektiğinde buna ihtiyaç vardır.
Guffa

NET'te belleğin doğru şekilde hizalanmaması konusunda ne sıklıkla endişelenmeniz gerekir? Birlikte çalışabilen şeyler mi?
Skurmedel

1
@zebrabox: Spesifikasyon bu uyarıyı listelemiyor, çok net bir ifade veriyor. Bir referans okuma veya yazmanın atomik olamadığı hafıza ile hizalı olmayan bir durum için bir referansınız var mı? Görünüşe göre bu, şartnamedeki çok açık dili ihlal ediyor.
TJ Crowder

6

Interlocked.Exchange <T>

Belirtilen T türündeki bir değişkeni belirtilen bir değere ayarlar ve orijinal değeri atomik bir işlem olarak döndürür.

Orijinal değeri değiştirir ve döndürür, işe yaramaz çünkü sadece onu değiştirmek istiyorsunuz ve Guffa'nın dediği gibi, zaten atomik.

Bir profil oluşturucunun uygulamanızda bir darboğaz olduğu kanıtlanmadığı sürece, kilitleri kaldırmayı düşünmelisiniz, kodunuzun doğru olduğunu anlamak ve kanıtlamak daha kolaydır.


3

Iterlocked.Exchange() sadece atomik değildir, aynı zamanda bellek görünürlüğünü de sağlar:

Aşağıdaki senkronizasyon işlevleri, bellek sırasını sağlamak için uygun engelleri kullanır:

Kritik bölümlere giren veya çıkan işlevler

Senkronizasyon nesnelerine sinyal veren işlevler

Bekleme işlevleri

Kilitli işlevler

Senkronizasyon ve Çok İşlemcili Sorunlar

Bu, atomikliğe ek olarak şunları da sağladığı anlamına gelir:

  • Onu çağıran iş parçacığı için:
    • Talimatların yeniden sıralanması yapılmaz (derleyici, çalışma zamanı veya donanım tarafından).
  • Tüm konular için:
    • Bu talimattan önce gerçekleşen belleğe okuma, bu talimatın yapılan değişikliği göremez.
    • Bu talimattan sonraki tüm okumalar, bu talimatla yapılan değişikliği görecektir.
    • Bu komuttan sonra tüm yazmalar, bu komut değişikliği ana belleğe ulaştıktan sonra gerçekleşir (bu komut, tamamlandığında bu komutu ana belleğe temizleyerek ve donanımın kendi açık zamanlamasını temizlemesine izin vermeyerek).
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.