2016-03-26 15 views
1

C++ 11'de, bir nesnenin kilitlenmeyen bir önbelleğini uygulamak için herhangi bir yolu var mıdır, bu birden çok iş parçacığından erişilebilecek güvenli midir? Önbelleğe bakıyorum hesaplama süper ucuz değil, aynı zamanda süper pahalı değil, bu yüzden bir kilit gerektiren benim durumumda önbellekleme amacı yenmek olurdu. IIUC, std::atomic'un kilitsiz olduğu garanti edilmez.C++ 11'de kilitlenmeyen önbellek uygulaması

Düzenleme: Pahalı olmayan bir hesaplama yaptığından, aslında bir veya iki kereden fazla çalışıp çalışmadığını umursamıyorum. Ancak, tüm tüketicilerin doğru değeri almasını sağlamalıyım. Aşağıdaki naif örnekte, bu durum garanti edilemez çünkü bir iş parçacığı için m_val başlatılmamış bir değer elde etmek mümkündür, çünkü m_alreadyCalculated başka bir iş parçacığı kümesi true değerine ayarlanmış, ancak m_val 'un değerini henüz belirlememiştir.

Düzenleme2: Aşağıdaki yorumlar temel türlerde std::atomic muhtemelen kilitlenmeyeceğine dikkat çekmektedir. Olması durumunda, m_alreadyCalculated'un m_val'un değeri ayarlanmadan önce true değerine ayarlanmasının mümkün olmadığından emin olmak için C++ 11'in bellek siparişini kullanmanın aşağıdaki örnekte doğru yolu nedir?

Sigara evreli önbellek örneği: Eğer uygulama bu lockfree yapabileceğini görmek için std::atomic<T>::is_lock_free() veya std::atomic::is_always_lock_free() kontrol edebilirsiniz olsa

class C { 
public: 
    C(int param) : m_param(param) {} 

    getValue() { 
     if (!m_alreadyCalculated) { 
      m_val = calculate(m_param); 
      m_alreadyCalculated = true; 
     } 
     return m_val; 
    } 

    double calculate(int param) { 
     // Some calculation 
    } 

private: 
    int m_param; 
    double m_val; 
    bool m_alreadyCalculated = false; 
} 
+0

Bu sesler ne yaptığını tam olarak Oldukça özel bir uygulama gibi, std :: atomic 'in kilitli olması garantileniyor mu, yoksa sadece istediğiniz platform (lar) ve derleyicilerin (ler) üzerinde kilitlenmesini önemsiyor musunuz? – hyde

+1

Her neyse, önbellek yararı çok küçükse, bir mutex kullanarak bu amacı bozar, o zaman bir spin kilit kullanarak muhtemelen mümkün olacaktır. – hyde

+0

Kilitlenmemesi için std :: atomic'e ihtiyacım var. – Danra

cevap

0

std :: atom, lockfree edilecek garanti edilmez.

Başka bir yaklaşım std::call_once kullanıyor olabilir, ancak benim anlayışımdan, bu diğer konuları engellemek anlamına geldiğinden daha da kötüdür.

Bu durumda, hem m_val hem de zaten Hesaplanan için std :: atomic ile giderdim. Hangi 2 (veya daha fazla) iş parçacığının aynı sonucu hesapladığı riskini içerir.

class C { 
public: 
    double getValue() { 
     if (alreadyCalculated == true) 
     return m_val; 

     bool expected = false; 
     if (calculationInProgress.compare_exchange_strong(expected, true)) { 
     m_val = calculate(m_param); 
     alreadyCalculated = true; 
     // calculationInProgress = false; 
     } 
     else { 
    // while (calculationInProgress == true) 
     while (alreadyCalculated == false) 
      ; // spin 
     } 
     return m_val; 
    } 

private: 
    double m_val; 
    std::atomic<bool> alreadyCalculated {false}; 
    std::atomic<bool> calculationInProgress {false}; 
}; 

Aslında kilidi serbest değil içeride bir dönme kilidi vardır: olarak

1

şey düşünün. Ancak, birden çok iş parçacığı tarafından calculate() çalıştırmak istemiyorsanız, böyle bir kilitlemeyi önleyemezsiniz.

getValue() burada daha da karmaşıklaşır, ancak önemli olan, bir kez m_val hesaplandığında, her zaman ilk if ifadesinde hemen geri dönecektir.

GÜNCELLEME

performans nedenleriyle, aynı zamanda bir önbellek satırı boyutuna yastığı bütün sınıf yapmak iyi bir fikir olabilir.

GÜNCELLEME 2

(o yorumların tarafından işaretlenmiş) bu işaret orijinal cevap bir hata, teşekkür JVApen vardı. calculationInProgress değişkeni daha iyi bir şekilde calculationHasStarted olarak yeniden adlandırılacaktır.

Ayrıca, bu çözüm, calculate()'un bir istisna atmadığını varsayar.

+1

Aslında, hesaplamak() bir veya iki kez çok fazla çalışırsa, gerçekten çok pahalı olmadığı için umurumda değil. Sadece gerekenden daha fazla kez çalıştırmak istemiyorum. Sorumu yansıtacak şekilde düzenledim. – Danra

+0

Sadece getValue() 'nin _not-yet hesaplanmış_ kısmında önemlidir, yine de 'm_val' güncellemesini bir şekilde korumanız gerekir. Bu çözüm, IMO'yu hesaplamaktan çok daha fazladır, bu da burada fayda getirmeyecektir. Önemli, değer hesaplandıktan sonra 'getValue()' işlevini olabildiğince hızlı bir şekilde çalıştırmaktır. –

+0

Neden "Zaten Hesaplanan" atomik olmalı? – Danra

0

Buradaki tek bir teknik soruyu yanıtlamak için: Değerin bayraktan önce güncellendiğinden emin olmak için bayrağın sürüm semantiği ile güncelleştirilir.Yayımlama semantiğinin anlamı, bu güncellemenin, önceki tüm işlemlerden sonra gerçekleşmesi gerektiğidir. x86 üzerinde sadece böyle bir derleyici güncellemeden önce bariyer ve belleğe güncelleme, kayıt yapma, şu anlama gelir:

asm volatile("":::"memory"); 
*(volatile bool*)&m_alreadyCalculated = true; 

Ve bu atom grubu bırakma semantik