2012-03-15 16 views
52

mi verimli bir zaman aşımı içinSpinWait vs Uyku bekliyor. Hangisini Kullanmalı?

SpinWait.SpinUntil(() => myPredicate(), 10000) 

için 10000ms

veya

o SleepWait aşağıdaki çizgisinde örnek şey için aynı durumda için Thread.Sleep yoklama kullanmak daha verimli mi işlevi:

public bool SleepWait(int timeOut) 
{ 
    Stopwatch stopwatch = new Stopwatch(); 
    stopwatch.Start(); 
    while (!myPredicate() && stopwatch.ElapsedMilliseconds < timeOut) 
    { 
     Thread.Sleep(50) 
    } 
    return myPredicate() 
} 

Eğer 1sec üzerinde zaman aşımları hakkında konuşuyorsak, SpinWait'in tüm verimliliğinin iyi bir kullanım şekli olmayabileceğini gösterdi? Bu geçerli bir varsayım mı?

Hangi yaklaşımı tercih edersiniz ve neden? Daha da iyi bir yaklaşım var mı?


Güncelleme - daha spesifik olmak:

önünde bulundurarak sınırlı kapasiteye ulaştığında BlockingCollection uyuyan bir iplik Pulse yapmak için bir yolu var mı? Marc Gravel'in önerdiği gibi, meşgul olmaktan kaçınmayı tercih ederim.

cevap

39

iyi yaklaşım aktif şey (daha çok gerçek haline sahibi olmak için pasif yoklama yerine) gerçek olma algılamak bazı mekanizmalar sahip olmaktır; Bu, herhangi bir bekletme işlemi veya Task, Wait veya belki bir event olabilir ve kendiniz için abone olabilirsiniz. Tabii ki, "bir şey olana kadar bekle" yaparsanız, hala bir geri aramaolarak bitiyor kadar verimli değil, yani: bir iş parçacığı kullanmak gerekmez beklemek. Task bunun için ContinueWith'a sahiptir, yoksa sadece iş çıkışı event'u işten çıkarabilirsiniz. event, içeriğe bağlı olarak muhtemelen en basit yaklaşımdır. Bununla birlikte, Task, burada, "zaman aşımı ile bekle" ve "geri arama" mekanizmaları dahil olmak üzere, burada bahsettiğiniz çoğu şeyi zaten sağlıyor.

Ve evet, 10 saniye boyunca dönme büyük değildir. Geçerli kodunuz gibi bir şey kullanmak istiyorsanız ve kısa bir gecikme beklemek için bir nedeniniz varsa, ancak daha uzun bir süre için izin vermeniz gerekiyorsa (belki) 20ms için SpinWait ve geri kalanı için Sleep kullanın?


Yorumu yeniden; ile

private readonly object syncLock = new object(); 
public bool WaitUntilFull(int timeout) { 
    if(CollectionIsFull) return true; // I'm assuming we can call this safely 
    lock(syncLock) { 
     if(CollectionIsFull) return true; 
     return Monitor.Wait(syncLock, timeout); 
    } 
} 

, "koleksiyonuna geri koymak" kodunda:

.NET 4 SpinWait yılında
if(CollectionIsFull) { 
    lock(syncLock) { 
     if(CollectionIsFull) { // double-check with the lock 
      Monitor.PulseAll(syncLock); 
     } 
    } 
} 
+0

Teşekkürler, cevabınızı beğendim, bu gerçekten güzel olurdu. Bazı kaynakların bir BlockingCollection aracılığıyla kullanımını izlediğinizi varsayalım. Yeniden kullanılırlar ve tekrar kullanıma hazır olduklarında koleksiyona geri dönerler. Bir kapatma, yalnızca bunların tümü koleksiyona geri döndüğünde gerçekleşebilir. Yoğun bir bekleyiş dışında bir kapamanın devam edebileceğini (yani koleksiyonun tekrar dolduğunu) bildirmek nasıl olur? – Anastasiosyal

+0

@Anastasiosyal Koleksiyonunu kapsülleyecek ve sarıcının dolu olduğunda bir şeyler yapmasını sağlayacağım; aslında, bunun için muhtemelen bir 'Monitor' kullanıyorum (örnek ekleyeceğim) –

+0

BlockingQueue öğesinden inen bir 'ObjectPool' sınıfının yıkıcısında, BlockingCollection öğesinden tüm nesneleri birer birer poplayın ve bunları atın. Bu C# olduğu için, atılan nesnelerin sayısı havuz derinliğine eşit olana kadar, nil) öğesine alınan referansı ayarlayın. Tüm havuzlanmış nesneler daha sonra elden çıkarıldı, böylece sıraya el koyabilir ve kapatma sırasına devam etmek için dtordan geri dönebilirsiniz. Yoklama/meşgul beklemesi gerekli değil! Süreye biraz zaman aşımı koymak isteyebilirsiniz, böylece sızan bir nesne bir istisna (ya da bir şey) artar. –

97

CPU kullanımı yoğun gerçekleştirir burada bir kanca bütçeyi nasıl mekanizma "dolu" Verimden önce 10 kez tekrarlanır. Ancak bu döngülerden sonra numaralı çağrıya hemen numarasına dönmez; bunun yerine, belirli bir süre için CLR (esas olarak OS) aracılığıyla döndürmek için'yi çağırır.Bu zaman periyodu başlangıçta birkaç onnano saniyelikdir, ancak 10 iterasyon tamamlanana kadar her iterasyonda iki katına çıkar. Bu, sistemin koşullara (çekirdek sayısı vb.) Göre ayarlayabileceği eğirme (CPU-yoğun) fazı boyunca harcanan toplam süre içinde netlik/öngörülebilirlik sağlar. SpinWait çok uzun bir süre spin-verimli fazda kalırsa, diğer ipliklerin devam etmesini sağlamak için periyodik olarak uyuyacaktır (daha fazla bilgi için bkz. J. Albahari's blog). Bu süreç

Yani, SpinWait (aslında Thread.Yield ve Thread.Sleep arayarak) her dönüşte kendi zaman dilimini verir, bundan sonra tekrarlamalar bir dizi sayıya CPU kullanımı yoğun eğirme sınırlar ... meşgul çekirdeğini korumak garantilidir kaynak tüketimini düşürüyor. Ayrıca, kullanıcının tek bir çekirdek makinesi çalıştırıp çalıştımadığını ve bu durumda her döngüde verim aldığını tespit edecektir.

Thread.Sleep ile iş parçacığı engellendi. Ancak bu süreç, CPU açısından yukarıdaki kadar pahalı olmayacaktır.

+6

+1 SpinWait ile thread.Sleep arasındaki netlik için teşekkürler. – Anastasiosyal

+0

Bu Albahari'nin web sitesinden kopyalanmıştır. Referans alınmalıdır! – georgiosd

+1

Bu kopya verbatim değil, onun siteden etkilendi ve neden onun bloguna başvurdum. – MoonKnight