it-swarm-tr.com

Uçucu anahtar kelime C # dilinde ne zaman kullanılmalıdır?

Herhangi biri C # daki uçucu anahtar kelimeyi iyi bir şekilde açıklayabilir mi? Hangi problemleri çözüyor, hangileri çözmüyor? Hangi durumlarda beni kilitlemekten kurtarır?

279
Doron Yaacoby

Buna cevap verecek daha iyi bir insan olduğunu sanmıyorum Eric Lippert (orijinaline vurgu yapıyor): 

C # 'da "uçucu" sadece "derleyicinin ve... Aynı zamanda "işlemcilere söyleyin ., Diğer işlemcileri durdurmak ve önbelleğe".

Aslında, bu son parça bir yalan. Uçucu anlamların gerçek anlamını okur. ve yazılar burada ana hatlarıyla belirttiğimden çok daha karmaşık; içinde gerçekte her işlemcinin neyi durdurduğunu garanti etmez. yapıyor ve önbellekleri ana belleğe/belleğe günceller. Aksine, sağlarlar. zayıf okuma ve okumadan önce ve sonra belleğe nasıl erişildiğini garanti eder. ..__ birbirlerine göre sipariş verildiği gözlenebilir. Yeni bir iş parçacığı oluşturma, bir kilit girme ya da .__ gibi bazı işlemler. Interlocked yöntem ailesinden birini kullanarak daha güçlü tanıtmak. Siparişin gözlemlenmesi ile ilgili garantiler. Daha fazla ayrıntı istiyorsanız, C # 4.0 şartnamesinin 3.10 ve 10.5.3 bölümlerini okuyun.

Açıkçası, sizi hiç zaman zaman geçici bir alan yapmaktan caydırıyorum. Uçucu alanlar düpedüz delice bir şey yaptığınızın bir işaretidir: siz iki farklı konuya aynı değeri okumaya ve yazmaya çalışıyorum. yerine kilit koymadan. Kilitler, hafızanın or. kilit içinde değiştirilmiş tutarlı olduğu gözlenir, kilitler garanti bu sadece bir iş parçacığının belirli bir hafıza yığınına aynı anda eriştiğini, ve böylece. üzerinde. Bir kilidin çok yavaş olduğu durumların sayısı çok fazladır. küçük ve yanlış kod alma ihtimaliniz. Çünkü tam olarak bellek modelinin çok büyük olduğunu anlamıyorsunuz. BEN en önemsiz .__ dışında herhangi bir düşük kilit kodu yazmaya çalışmayın. Kilitli işlemlerin kullanımları. "Uçucu" kullanımını .__'ya bırakıyorum. gerçek uzmanlar.

Daha fazla okumak için bakınız:

254
Ohad Schneider

Geçici anahtar kelimenin ne yaptığı hakkında biraz daha teknik bilgi edinmek 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 (serbest 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 depolamak için ecx register'ı kullanmaya karar verdi. Uçucu olmayan döngü için (ilk) derleyici i'yi eax register'a atadı. Yeterince açıksözlü. Yine de birkaç ilginç bit var - lea ebx, [ebx] komutu etkin bir şekilde çok baytlı bir komuttur, böylece döngü 16 baytlık bir hizalanmış hafıza adresine atlar. Diğeri, inc eax komutunu kullanmak yerine döngü sayacını artırmak için edx kullanımıdır. Eklenti reg, reg komutu, inc reg komutuna göre birkaç IA32 çekirdeğinde düşük gecikmeye sahiptir, ancak hiçbir zaman daha yüksek gecikmeye sahip değildir. 

Şimdi uçucu döngü sayacı ile döngü için. Sayaç [esp] konumunda saklanır ve geçici anahtar kelime derleyiciye, değerin her zaman bellekten okunması/yazılması ve asla bir kayıt defterine atanmaması gerektiğini söyler. Derleyici, sayaç değerini güncellerken üç ayrı adımda (yük eax, inc eax, eax kaydetme) bir yük/artış/saklama yapamayacak kadar ileri gider, bunun yerine bellek doğrudan tek bir komutta değiştirilir (bir ek not reg). Kodun yaratılma şekli, döngü sayacının değerinin tek bir CPU çekirdeği bağlamında her zaman güncel olmasını sağlar. Veri üzerinde hiçbir işlem bozulma veya veri kaybına neden olamaz (bu nedenle, inc/inc sırasında değer değişebileceği için depoda kaybedildiği için load/inc/store kullanmayın). Kesintiler ancak geçerli talimat tamamlandığında servis verilebildiğinden, hizalanmamış bellek olsa bile veriler hiçbir zaman bozulmaz.

Sisteme ikinci bir CPU eklediğinizde, geçici anahtar kelime başka bir CPU tarafından aynı anda güncellenen verilere karşı koruma sağlamaz. Yukarıdaki örnekte, potansiyel bir yolsuzluk elde etmek için verilerin atanmamış olması gerekir. Volatile anahtar sözcüğü, veriler atomik olarak işlenemiyorsa, örneğin döngü sayacı uzun süre (64 bit) türdeyse (64 bit) öyleyse, değeri ortasında güncellemek için iki adet 32 ​​bitlik işlem gerektirir hangi kesmenin gerçekleşebileceği ve verileri değiştirebileceği.

Bu nedenle, geçici anahtar kelime yalnızca işlemlerin her zaman atomik olduğu yerel kayıtların boyutuna eşit veya daha az olan hizalı veriler için iyidir.

Volatile anahtar sözcüğü, IO 'nun sürekli değişeceği ancak bir bellek eşlemeli IO aygıtı gibi sabit bir adrese sahip olduğu ve derleyicinin yapmaması gereken UART işlemleriyle kullanılmak üzere tasarlandı. adresden okunan ilk değeri tekrar kullanmaya devam edin.

Büyük verileri kullanıyorsanız veya birden fazla CPU'ya sahipseniz, veri erişimini doğru bir şekilde ele almak için daha yüksek bir seviyeye (OS) kilitleme sistemine ihtiyacınız olacaktır.

54
Skizz

.NET 1.1 kullanıyorsanız, çift denetimli kilitleme yaparken geçici anahtar kelime gerekir. Niye ya? .NET 2.0 öncesinde, aşağıdaki senaryo, ikinci bir iş parçacığının boş olmayan, ancak tam olarak oluşturulmuş bir nesneye erişmesine neden olabilir:

  1. 1. iş parçacığı bir değişkenin null olup olmadığını sorar . // if (this.foo == null)
  2. İş parçacığı 1 değişkeninin null olduğunu belirler, bu nedenle bir kilit girer // kilit (this.bar)
  3. Konu 1 değişkeni null ise .. AGAIN sorar . // if (this.foo == null)
  4. İş parçacığı 1, değişken null olduğunu hala belirler, bu yüzden bir yapıcı çağırır ve değeri değişkene atar . // this.foo = new Foo ();

.NET 2.0'dan önce, yapıcının çalışması bitmeden önce this.foo'ya yeni Foo örneği atanabilir. Bu durumda, ikinci bir iş parçacığı girebilir (1. iş parçacığının Foo'nun kurucusuna yaptığı çağrı sırasında) ve aşağıdakileri deneyimleyebilirsiniz:

  1. 2. iş parçacığı değişken null olup olmadığını sorar. // if (this.foo == null)
  2. İş parçacığı 2 değişkeni boş değil, bu nedenle kullanmaya çalışır . // this.foo.MakeFoo ()

.NET 2.0 öncesinde, bu sorunu gidermek için this.foo dosyasını 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ız gerekmez.

Wikipedia aslında Double Checked Locking hakkında iyi bir makale yayınladı ve bu konu hakkında kısaca değindi: http://en.wikipedia.org/wiki/Double-checked_locking

38
AndrewTek

MSDN : .__ 'dan itibaren. Uçucu değiştirici, erişimi seri hale getirmek için lock deyimini kullanmadan birden fazla iş parçacığı tarafından erişilen bir alan için kullanılır. Geçici değiştiriciyi kullanmak, bir iş parçacığının başka bir iş parçacığı tarafından yazılmış en güncel değeri almasını sağlar.

22
Dr. Bob

Bazen, derleyici bir alanı optimize eder ve saklamak için bir kayıt kullanır. Eğer iş parçacığı 1 alana bir yazı yazarsa ve güncelleme bir kayıt defterinde saklandığından (hafızaya alınmadığından) başka bir iş parçacığı bu konuya erişirse, ikinci iş parçacığı eski veriler alır.

Uçucu anahtar kelimeyi derleyiciye "Bu değeri bellekte saklamanızı istiyorum" yazdığını düşünebilirsiniz. Bu, 2. iş parçacığının en son değeri almasını garanti eder.

20
Benoit

CLR yönergeleri optimize etmeyi sever, bu nedenle koddaki bir alana eriştiğinizde daima alanın mevcut değerine erişemeyebilir (yığıntan vb. Olabilir). Bir alanı volatile olarak işaretlemek, alanın geçerli değerine komut tarafından 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 (kilitlenmeyen bir senaryoda) değiştirilebilirse faydalıdır.

Açıkçası bazı optimizasyonları kaybedersiniz, ancak kodu daha basit tutar.

13
Joseph Daigle

Derleyici bazen kodu optimize etmek için kod sırasını değiştirir. Normalde bu, tek iş parçacıklı ortamda bir sorun değildir, ancak çok iş parçacıklı ortamda bir sorun olabilir. Aşağıdaki örneğe bakınız:

 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 çıktı veya "Değer: 10" beklemeniz gerekmez. Derleyici, t1 fonksiyonunun içindeki satırı değiştiriyor olabilir. Eğer t2 çalıştırırsa, _flag değeri 5 olabilir, ancak _ değeri 0 olur. Beklenen mantık bozulabilir. 

Bunu düzeltmek için alana uygulayabileceğiniz volatile anahtar sözcüğünü 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;

volatile komutunu yalnızca gerçekten ihtiyacınız olursa kullanmalısınız, çünkü belirli derleyici optimizasyonlarını devre dışı bıraktığı için performansa zarar verir. Ayrıca tüm .NET dilleri tarafından desteklenmez (Visual Basic bunu desteklemez), bu nedenle dil birlikte çalışabilirliğini engeller.

0
Aliaksei Maniuk

Tüm bunları özetlemek gerekirse, sorunun doğru cevabı şudur: Kodunuz 2.0 çalışma zamanında veya daha sonra çalışıyorsa, geçici anahtar kelime neredeyse hiçbir zaman gerekli değildir ve gereksiz yere kullanıldığında yarardan çok zarar verir. I.E. Hiç kullanma. AMA çalışma zamanının önceki sürümlerinde, statik alanlarda düzgün çift kontrol kilitlemesi için IS gerekliydi. Özellikle, sınıfı statik sınıf başlatma koduna sahip statik alanlar.

0
Paul Easter