2013-08-16 20 views
8

Piszę prosty program do testowania wątku w C++ 11, ale std::cout nie działa tak, jak oczekuję.przy użyciu std :: cout w wielu wątkach

class Printer 
{ 
public: 
    void exec() 
    { 
     mutex m; 
     m.lock(); 
     cout<<"Hello "<<this_thread::get_id()<<endl; 
     chrono::milliseconds duration(100); 
     this_thread::sleep_for(duration); 
     m.unlock(); 

    } 
}; 

int main() 
{ 
    Printer printer; 

    thread firstThread([&printer](){ 
     while(1) 
      printer.exec(); 

    }); 
    thread secondThread([&printer](){ 
     while(1) 
      printer.exec(); 
    }); 

    firstThread.join(); 
    secondThread.join();  
} 

niektóre wyniki:

Hello 11376 
Hello 16076 
Hello 16076 
Hello Hello 11376 
16076 
Hello 11376 
,.... 

użyłem mutex do blokowania wątki więc nie mogę zrozumieć dlaczego dwa wątki realizują std::cout w tym samym czasie. To szwy bardzo zmęczony mnie. Czy ktoś wytłumaczy, co się dzieje!?!

+1

Po wdrożeniu rozwiązania przewidzianego w odpowiedzi powinieneś również przejść do używania 'lock_guard's zamiast ręcznego blokowania/odblokowywania połączeń. I nie przejmuj się spłukiwaniem (np. Z 'endl'); po prostu użyj "\ n". – bames53

Odpowiedz

21

nici są za pomocą różnemutex przypadki jak mutex jest lokalna zmienna w funkcji exec() blokując w ten sposób mutex jest bezcelowe, ponieważ każda nitka będzie blokowania jej własne mutex skutkującego brakiem synchronizacji pomiędzy gwintami. Ta sama instancja mutex musi być używana przez wątki w celu uzyskania synchronizacji.

Aby poprawić w opublikowanym kodzie, ustaw zmienną składową mutex. Jeśli jednak utworzono inny obiekt Printer, nie byłoby synchronizacji między wątkami, które używały różnych instancji Printer. W tym przypadku, mutex musiałby być zmienną static member aby zapewnić synchronizację:

class Printer 
{ 
public: 
    //... 
private: 
    static std::mutex mtx_; 
}; 

std::mutex Printer::mtx_; 

Aby zapewnić mutex jest zawsze zwolniony, niezależnie od tego czy funkcja zakończy się normalnie lub poprzez wyjątek, użyj std:lock_guard:

std::lock_guard<std::mutex> lock(m); // 'm' locked, and will be 
            // unlocked when 'lock' is destroyed. 
std::cout<< "Hello " << std::this_thread::get_id() << std::endl; 
std::chrono::milliseconds duration(100); 
std::this_thread::sleep_for(duration); 
+0

Fix jest deklaracja 'mutex' z' static' czas przechowywania. ... albo to. – Casey

+0

@Casey lub zmienną składową, ponieważ wątki używają tej samej instancji 'Printer'. – hmjd

+1

@hjmd Wolałbym słowo 'statyczne' w tym przypadku odpowiadające zakresowi' std :: cout'. W ten sposób będzie działać, gdy jakiś programista w dół tworzy kolejną 'drukarkę' w innej funkcji. – Casey

1

można rozważyć globalny std::mutex cout_mutex; (gdzieś w przestrzeni nazw), który służy do chronionego std::cout wyjściu. Upewnij się, że używasz std::lock<std::mutex> (abyś nie mógł zapomnieć o odblokowaniu muteksu i bezpieczeństwie wyjątków).

11

Przyjęta odpowiedź jest prawidłowa. Jednak miło jest rozdzielić wątpliwości:

  1. Potrzebujesz sposobu drukowania do std::cout w sposób bezpieczny dla wątków.
  2. Musisz utworzyć obiekty/funktory/funkcje, aby działały w wątkach i uruchamiać je.

Oto narzędzie używam tego właśnie koncentruje się na gromadzeniu argumentów std::cout i strumieniowego się pod static std::mutex:

#include <iostream> 
#include <mutex> 

std::ostream& 
print_one(std::ostream& os) 
{ 
    return os; 
} 

template <class A0, class ...Args> 
std::ostream& 
print_one(std::ostream& os, const A0& a0, const Args& ...args) 
{ 
    os << a0; 
    return print_one(os, args...); 
} 

template <class ...Args> 
std::ostream& 
print(std::ostream& os, const Args& ...args) 
{ 
    return print_one(os, args...); 
} 

std::mutex& 
get_cout_mutex() 
{ 
    static std::mutex m; 
    return m; 
} 

template <class ...Args> 
std::ostream& 
print(const Args& ...args) 
{ 
    std::lock_guard<std::mutex> _(get_cout_mutex()); 
    return print(std::cout, args...); 
} 

Kod ten może być ponownie użyty do strumieni innych niż std::cout, ale powyższe jest specjalizuje się tylko w celu docelowym std::cout. Dzięki temu Twój Printer::exec() może teraz być znacznie uproszczona:

void exec() 
{ 
    print("Hello ", std::this_thread::get_id(), '\n'); 
    std::this_thread::sleep_for(std::chrono::milliseconds(100)); 
} 

Teraz nie tylko swoją Printer użycie cout w THREADSAFE sposób i została uproszczona (npnie musi utrzymywać własnego mutex dla cout), ale wszystkie inne typy i funkcje mogą również używać cout i wszystkie współpracują ze sobą w sposób bezpieczny. Funkcja print sama w sobie zachowuje teraz mutex, a fakt ten jest obudowany z dala od wszystkich klientów print.

+5

print jest funkcją szablonu. Myślę, że mógłbyś skończyć z więcej niż jednym muteksem. –

+1

@JanChristophUhde: Doskonała obserwacja! A to zajęło 3 lata, żeby ktoś to zauważył! :-) Naprawiony. Dzięki. –

3

Dzielę się sztuczką z Nicolasa podaną w this question, którą uważam za bardziej elegancką niż implementacja Howarda Hinnanta. Chodzi o stworzenie tymczasowego obiektu ostringstream i umieszczenie kodu ochronnego na destruktorze.

/** Thread safe cout class 
    * Exemple of use: 
    * PrintThread{} << "Hello world!" << std::endl; 
    */ 
class PrintThread: public std::ostringstream 
{ 
public: 
    PrintThread() = default; 

    ~PrintThread() 
    { 
     std::lock_guard<std::mutex> guard(_mutexPrint); 
     std::cout << this->str(); 
    } 

private: 
    static std::mutex _mutexPrint; 
}; 

std::mutex PrintThread::_mutexPrint{}; 

Następnie można go używać jako zwykły std::cout z dowolnego wątku:

PrintThread{} << "val = " << 33 << std::endl; 

Celem zbierania danych jako stałego std::ostringstream. Gdy tylko osiągnięta zostanie śpiączka, obiekt zostaje zniszczony i wypłukuje wszystkie zebrane informacje.

Powiązane problemy