Herkes C # volatile anahtar kelime için iyi bir açıklama sağlayabilir? Hangi problemleri çözer, hangilerini çözmez? Hangi durumlarda kilitleme kullanımını kurtarır?
Herkes C # volatile anahtar kelime için iyi bir açıklama sağlayabilir? Hangi problemleri çözer, hangilerini çözmez? Hangi durumlarda kilitleme kullanımını kurtarır?
Yanıtlar:
Bunu cevaplamak için Eric Lippert'den daha iyi bir kişi olduğunu sanmıyorum (orijinalde vurgu):
C # 'da "uçucu" sadece "derleyici ve titreşimin herhangi bir kod yeniden sıralaması yapmadığından veya bu değişken üzerinde önbellek optimizasyonlarını kaydetmediğinden emin olun" anlamına gelir. Ayrıca, "diğer işlemcileri durdurup ana belleği önbellekleriyle senkronize etmeleri anlamına gelse bile, işlemcilere en son değeri okuduğumdan emin olmak için yapmaları gereken her şeyi yapmalarını söyleyin" anlamına gelir.
Aslında, bu son parça bir yalan. Uçucu okuma ve yazmaların gerçek anlambilimi, burada özetlediğimden çok daha karmaşıktır; aslında onlar her işlemci ne yaptığını durur aslında garanti yok ana bellekten / 'e ve güncellemeleri önbelleğe. Aksine, okuma ve yazma işlemlerinden önce ve sonra belleğe nasıl erişildiğine dair daha zayıf garantiler sağlarlar . Yeni bir iş parçacığı oluşturma, bir kilit girme veya Kilitli yöntem ailesinden birini kullanma gibi belirli işlemler, siparişin gözlemlenmesi konusunda daha güçlü garantiler sağlar. Daha fazla ayrıntı istiyorsanız, C # 4.0 spesifikasyonunun 3.10 ve 10.5.3 bölümlerini okuyun.
Açıkçası, seni hiç dalgalı bir alan yapmaktan caydırıyorum . Uçucu alanlar, çılgınca bir şey yaptığınızın bir işaretidir: bir kilit yerine koymadan aynı değeri iki farklı iş parçacığında okumaya ve yazmaya çalışıyorsunuz. Kilitler, kilidin içinde okunan veya değiştirilen belleğin tutarlı olarak gözlemlendiğini, kilitler ise bir seferde belirli bir belleğe yalnızca bir iş parçacığının eriştiğini garanti eder. Bir kilidin çok yavaş olduğu durumların sayısı çok azdır ve tam bellek modelinin çok büyük olduğunu anlamadığınız için kodu yanlış alma olasılığınız çok yüksektir. Kilitli işlemlerin en önemsiz kullanımları dışında düşük kilitli kod yazmaya çalışmıyorum. "Uçucu" kullanımını gerçek uzmanlara bırakıyorum.
Daha fazla okuma için bakınız:
volatile
kilit sayesinde orada olacak
Geçici anahtar kelimenin ne yaptığı hakkında biraz daha teknik bilgi almak istiyorsanız, aşağıdaki programı göz önünde bulundurun (DevStudio 2005 kullanıyorum):
#include <iostream>
void main()
{
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
j += i;
}
for (volatile int i = 0 ; i < 100 ; ++i)
{
j += i;
}
std::cout << j;
}
Standart optimize (bırakma) derleyici ayarlarını kullanarak, derleyici aşağıdaki derleyiciyi (IA32) oluşturur:
void main()
{
00401000 push ecx
int j = 0;
00401001 xor ecx,ecx
for (int i = 0 ; i < 100 ; ++i)
00401003 xor eax,eax
00401005 mov edx,1
0040100A lea ebx,[ebx]
{
j += i;
00401010 add ecx,eax
00401012 add eax,edx
00401014 cmp eax,64h
00401017 jl main+10h (401010h)
}
for (volatile int i = 0 ; i < 100 ; ++i)
00401019 mov dword ptr [esp],0
00401020 mov eax,dword ptr [esp]
00401023 cmp eax,64h
00401026 jge main+3Eh (40103Eh)
00401028 jmp main+30h (401030h)
0040102A lea ebx,[ebx]
{
j += i;
00401030 add ecx,dword ptr [esp]
00401033 add dword ptr [esp],edx
00401036 mov eax,dword ptr [esp]
00401039 cmp eax,64h
0040103C jl main+30h (401030h)
}
std::cout << j;
0040103E push ecx
0040103F mov ecx,dword ptr [__imp_std::cout (40203Ch)]
00401045 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402038h)]
}
0040104B xor eax,eax
0040104D pop ecx
0040104E ret
Çıktıya bakıldığında derleyici, j değişkeninin değerini saklamak için ecx yazmacı kullanmaya karar verdi. Uçucu olmayan döngü için (ilk) derleyici eax kaydına i atamıştır. Yeterince açıksözlü. Yine de birkaç ilginç bit var - lea ebx, [ebx] komutu etkili bir şekilde çok baytlı bir nop talimatıdır, böylece döngü 16 baytlık hizalanmış bir bellek adresine atlar. Diğeri, inc eax komutunu kullanmak yerine döngü sayacını artırmak için edx kullanımıdır. Add reg, reg komutunun, birkaç IA32 çekirdeğinde, inc komutuna kıyasla daha düşük gecikme süresi vardır, ancak hiçbir zaman daha yüksek gecikme süresi yoktur.
Şimdi uçucu döngü sayacı olan döngü için. Sayaç [esp] dizininde saklanır ve geçici anahtar sözcük derleyiciye değerin her zaman belleğe okunması / belleğe yazılması ve hiçbir zaman bir kayıt defterine atanmaması gerektiğini bildirir. Derleyici, sayaç değerini güncellerken üç ayrı adım (yük eax, inc eax, eax kaydet) olarak bir yük / artış / depolama yapmayacak kadar ileri gider, bunun yerine bellek doğrudan tek bir komutta değiştirilir (bir ek bellek reg). Kodun oluşturulma şekli, döngü sayacının değerinin her zaman tek bir CPU çekirdeği bağlamında güncel olmasını sağlar. Veriler üzerinde hiçbir işlem bozulmaya veya veri kaybına yol açamaz (bu nedenle, yük sırasında / inc / store kullanılmaz, çünkü inc sırasındaki değer değişebilir ve böylece mağazada kaybolabilir). Kesmeler ancak mevcut talimat tamamlandıktan sonra servis edilebildiğinden,
Sisteme ikinci bir CPU eklediğinizde, geçici anahtar kelime aynı anda başka bir CPU tarafından güncellenen verilere karşı koruma sağlamaz. Yukarıdaki örnekte, potansiyel bir bozulma elde etmek için verilerin atanmamış olması gerekir. Uçucu anahtar kelime, veriler atomik olarak işlenemezse olası bozulmayı engellemez; örneğin, döngü sayacı uzun (64 bit) türdeyse, değeri güncellemek için iki adet 32 bit işlem gerekir. bir kesinti meydana gelebilir ve verileri değiştirebilir.
Bu nedenle, uçucu anahtar sözcük yalnızca işlemler her zaman atomik olacak şekilde yerel kayıtların boyutundan küçük veya ona eşit olan hizalanmış veriler için iyidir.
Uçucu anahtar kelime, IO'nun sürekli değişeceği ancak bellek eşlemeli UART cihazı gibi sabit bir adresin bulunduğu IO işlemleriyle kullanılmak üzere tasarlandı ve derleyici adresten okunan ilk değeri tekrar kullanmaya devam etmemelidir.
Büyük verileri işliyorsanız veya birden fazla CPU'nuz varsa, veri erişimini düzgün bir şekilde işlemek için daha yüksek bir düzey (OS) kilitleme sistemine ihtiyacınız olacaktır.
.NET 1.1 kullanıyorsanız, çift denetimli kilitleme yaparken geçici anahtar sözcük gerekir. Neden? .NET 2.0'dan önce, aşağıdaki senaryo ikinci bir iş parçacığının boş olmayan, ancak tam olarak oluşturulmamış bir nesneye erişmesine neden olabilir:
.NET 2.0'dan önce, this.foo'ya, kurucunun çalışması bitmeden yeni Foo örneği atanabilir. Bu durumda, ikinci bir iş parçacığı gelebilir (iş parçacığının Foo'nun yapıcısına yaptığı çağrı sırasında) ve aşağıdakilerle karşılaşabilirsiniz:
.NET 2.0'dan önce, this.foo'yu bu soruna geçici bir çözüm bulmak için geçici olarak bildirebilirsiniz. .NET 2.0'dan bu yana, çift denetimli kilitlemeyi gerçekleştirmek için artık geçici anahtar sözcüğü kullanmanıza gerek yoktur.
Wikipedia'nın Çift Kontrollü Kilitleme hakkında iyi bir makalesi var ve bu konuya kısaca değiniyor: http://en.wikipedia.org/wiki/Double-checked_locking
foo
? Diş 1 kilitlenmez this.bar
ve bu nedenle yalnızca diş 1, foo'yu zamanında verilen bir noktada başlatabilir mi? Yani, kilit tekrar bırakıldıktan sonra değeri kontrol edersiniz, yine de diş 1'den yeni bir değere sahip olmalıdır.
Derleyici bazen bir alanı optimize eder ve saklamak için bir kayıt kullanır. İş parçacığı 1 alana yazma yaparsa ve başka bir iş parçacığı bu alana erişirse, güncelleştirme bir kayıtta (bellekte değil) depolandığından, 2. iş parçacığı eski verileri alır.
Uçucu anahtar kelimeyi "Bu değeri hafızada saklamanızı istiyorum" derleyicisine söyleyerek düşünebilirsiniz. Bu, 2. iş parçacığının en son değeri almasını garanti eder.
Gönderen MSDN : Uçucu değiştirici genellikle serialize erişimine kilit deyimi kullanmadan birden çok iş parçacığı tarafından erişilen bir alan için kullanılır. Uçucu değiştiriciyi kullanmak, bir iş parçacığının başka bir iş parçacığı tarafından yazılan en güncel değeri almasını sağlar.
CLR, talimatları optimize etmeyi sever, bu nedenle koddaki bir alana eriştiğinizde, alanın her zaman geçerli değerine erişmeyebilir (yığından vb. Olabilir). Bir alanı işaretlemek, alanın volatile
geçerli değerine talimatla erişilmesini sağlar. Bu değer, programınızdaki eşzamanlı bir iş parçacığı veya işletim sisteminde çalışan başka bir kod tarafından değiştirilebiliyorsa (kilitlemesiz bir senaryoda).
Açıkçası bazı optimizasyonları kaybedersiniz, ancak kodu daha basit tutar.
Joydip Kanjilal'ın bu makalesini çok yardımcı buldum !
When you mark an object or a variable as volatile, it becomes a candidate for volatile reads and writes. It should be noted that in C# all memory writes are volatile irrespective of whether you are writing data to a volatile or a non-volatile object. However, the ambiguity happens when you are reading data. When you are reading data that is non-volatile, the executing thread may or may not always get the latest value. If the object is volatile, the thread always gets the most up-to-date value
Referans için burada bırakacağım
Derleyici bazen en iyi duruma getirmek için koddaki ifadelerin sırasını değiştirir. Normalde bu tek iş parçacıklı ortamlarda bir sorun değildir, ancak çok iş parçacıklı ortamlarda bir sorun olabilir. Aşağıdaki örneğe bakın:
private static int _flag = 0;
private static int _value = 0;
var t1 = Task.Run(() =>
{
_value = 10; /* compiler could switch these lines */
_flag = 5;
});
var t2 = Task.Run(() =>
{
if (_flag == 5)
{
Console.WriteLine("Value: {0}", _value);
}
});
T1 ve t2'yi çalıştırırsanız, sonuç olarak hiçbir çıktı veya "Değer: 10" beklemezsiniz. Derleyici t1 fonksiyonu içindeki hattı değiştirebilir. T2 yürütülürse, _flag değeri 5 olabilir, ancak _value 0 olabilir. Bu nedenle beklenen mantık bozulabilir.
Bunu düzeltmek için alana uygulayabileceğiniz geçici bir anahtar kelime kullanabilirsiniz . Bu ifade, derleyici optimizasyonlarını devre dışı bırakır, böylece kodunuzda doğru sırayı zorlayabilirsiniz.
private static volatile int _flag = 0;
Bazı derleyici optimizasyonlarını devre dışı bıraktığından, performansı gerçekten olumsuz etkileyeceğinden, uçucuyu yalnızca gerçekten ihtiyacınız varsa kullanmalısınız . Ayrıca tüm .NET dilleri tarafından desteklenmez (Visual Basic desteklemez), bu nedenle dil birlikte çalışabilirliğini engeller.
Tüm bunları özetlemek gerekirse, sorunun doğru cevabı şöyledir: Kodunuz 2.0 çalışma zamanında veya daha sonra çalışıyorsa, geçici anahtar kelimeye neredeyse hiç gerek yoktur ve gereksiz yere kullanıldığında yarardan daha fazla zarar verir. IE Hiç kullanma. ANCAK çalışma zamanının önceki sürümlerinde, statik alanlarda düzgün çift kontrol kilitleme için gereklidir. Sınıfı statik sınıf başlatma koduna sahip olan özellikle statik alanlar.
birden çok evre bir değişkene erişebilir. En son güncelleme değişken üzerinde olacak