2012-03-02 17 views
8

Bir oyun projesi için bazı çok iş parçacıklı kodlarla çalışıyorum ve aynı anda iletileri ayıklamak için cout'u kullanan iki iş parçacığı tarafından oluşturulan stdout kusmayla sıralamada biraz yoruldum. Biraz araştırma yaptım ve bir şeyle gelmeden önce bir iki saat boyunca bir duvara baktım. Aşağıdaki kod, zaman tutma ve iş parçacığı için SFML kullanır. SFML muteksleri sadece pencerelerde kritik bölümlere sarılır.İplik güvenli diş tekniği. Bir şey mi eksik?

Üstbilgi:

#include <SFML\System.hpp> 
#include <iostream> 

class OutputStreamHack 
{ 
    public: 
    OutputStreamHack(); 
    ~OutputStreamHack(); 

    ostream& outputHijack(ostream &os); 

    private: 
    sf::Clock myRunTime; 
    sf::Mutex myMutex; 
}; 

static OutputStream OUTHACK; 

ostream& operator<<(ostream& os, const OutputStreamHack& inputValue); 

Uygulama:

#include <SFML\System.hpp> 
#include <iostream> 

#include "OutputStreamHack.h" 

using namespace std; 

OutputStreamHack::OutputStreamHack() 
{ 
    myMutex.Unlock(); 
    myRunTime.Reset(); 
} 

OutputStreamHack::~OutputStreamHack() 
{ 
    myMutex.Unlock(); 
    myRunTime.Reset(); 
} 

ostream& OutputStreamHack::outputHijack(ostream &os) 
{ 

    sf::Lock lock(myMutex); 
    os<<"<"<<myRunTime.GetElapsedTime()<<","<<GetCurrentThreadId()<<"> "<<flush; 
    return os; 
} 

ostream& operator<<(ostream& os, const OutputStreamHack& inputValue) 
{ 
    OUTHACK.outputHijack(os); 
    return os; 
} 

Kullanım:

cout<<OUTHACK<<val1<<val2<<val3....<<endl; 

Ok, bu çalışma şekli kilitleyerek iplik güvenlik getirir bir aşırı ekleme operatörü olan Statik bir nesnede bir yineleyici, ardından arabelleği temizleme. Eğer süreci doğru bir şekilde anladım (çoğunlukla kendimi öğreten bir programcıyım), yerleştirme zincirinin elemanlarını uçtan başlayarak en başından sonuna kadar geçirir ve her bir elemanın akışa eklenmesi için bir aşağı akış değişkenini zincirden geçirir. OUTHACK öğesine ulaştığında, aşırı yüklenen operatör çağrılır, muteks kilitlenir ve akış boşaltılır.

Doğrulama için çıktıya zaman/iş parçacığı kimliği hata ayıklama bilgisi ekledim. Şimdiye kadar, testlerim bu yöntemin çalıştığını gösteriyor. Birden fazla argümanla cout'u çarptıran birkaç konu var ve her şey doğru sırada çıkıyor.

Bu konuyu araştırırken okuduklarımdan beri, diş ipinde emniyetin olmaması, insanların dişli programlamaya girerken karşılaştıkları oldukça yaygın bir sorun gibi görünüyor. Anlamaya çalıştığım teknik, kullanmakta olduğum teknik sorun için basit bir çözüm mü yoksa akıllı olduğumu düşünürken önemli bir şey eksik olduğunu düşünmekteyim.

Deneyimlerime göre, programlamayı tanımlamak için kullanıldığında akıllı sözcük, gecikmiş acı için sadece bir kod kelimesidir. Burada bir şeye ya da etrafta dolanan hackerları kovalamak mı?

Teşekkürler!

+1

Çalışması gerçektir. Sadece zaman ve iş parçacığının kimliği, muteks tarafından korunur. OUTHACK ve "val1" arasında başka bir iş parçacığı gizlice almak mümkün. –

+1

İlk önce bir "ostringstream" 'e yazmayı, ardından içeriği bir işlemde "cout" a atmayı düşünün. 'cout' genellikle iş parçacığı güvenli olacak, sadece kilitleme her çağrı için açıktır, ve her bir '<<' işlemi ayrı bir çağrıdır .... Bu şekilde, uygulama kodunuzda ekstra bir kilitleme yoktur - bu sadece azalabilir paralellik. –

+0

İlgili: [Cout synchronized/thread-safe?] (Http://stackoverflow.com/questions/6374264/is-cout-synchronized-thread-safe/6374525#6374525) – legends2k

cevap

19

Burada iş parçacığı ne değildir? cout her seferinde değildir. Sırayla iki işlev çağrısını çağırıyor. std::cout << a << b, operator<<(std::cout, a) numaralı telefonu arayarak operator<<(std::cout, b) numarasına eşdeğerdir. Sırayla iki işlevi çağırmak atomik bir şekilde yürütüleceklerini garanti etmez.

Yalnızca, zaman ve iş parçacığının kimliğinin çıkışı, muteks tarafından korunur. OUTHACK ve val1'un eklenmesi arasında başka bir iplik parçasının gizlice girilmesi mümkün olur, çünkü kilit OUTHACK eklendikten sonra artık bekletilmez.

Değerdeki yıkıcı kilidini bir nesne tarafından OutputStreamHack dönüş için operator<< sahip olabilir. Tempolar her tam ifadenin sonuna kadar yaşadığı için, kod "noktalı virgül olana kadar" tutacaktır. Bununla birlikte, kopyalar söz konusu olabileceğinden, bu, bir hareket yapıcısı (veya C++ 03'te, auto_ptr'un gasp'a benzer bir özel kopya kurucusu) olmaksızın problemli olabilir.

cout'un mevcut iş parçacığı güvenliğini kullanmak için (C++ 11 dilinde dil garantisi vardır, ancak birçok uygulama daha önce işlenmemiş öğelerdir). Her şeyi bir std::stringstream üyesine akıtan bir nesne yapın ve ardından imha edildiğinde bir kerede hepsini yazın.

class FullExpressionAccumulator { 
public: 
    explicit FullExpressionAccumulator(std::ostream& os) : os(os) {} 
    ~FullExpressionAccumulator() { 
     os << ss.rdbuf() << std::flush; // write the whole shebang in one go 
    } 

    template <typename T> 
    FullExpressionAccumulator& operator<<(T const& t) { 
     ss << t; // accumulate into a non-shared stringstream, no threading issues 
     return *this; 
    } 

private: 
    std::ostream& os; 
    std::stringstream ss; 

    // stringstream is not copyable, so copies are already forbidden 
}; 

// using a temporary instead of returning one from a function avoids any issues with copies 
FullExpressionAccumulator(std::cout) << val1 << val2 << val3; 
+2

Ah, Düşüncemdeki boşluğu görüyorum . Cout davranışının yinelemeden ziyade tekrarlayıcı olduğu ve OUTHACK'in işlendiği noktada stdout ile birleştirilen yerel bir akım üzerinde çalıştığı varsayımı altında çalışıyordum. Tekniğiniz tam olarak neye benzediğimi andırıyor! Bir ton teşekkürler. – Chris

İlgili konular