2016-01-05 13 views
5

Modifica: Come sembra, il problema era che in realtà non creavo un'istanza locale di lock_guard, ma semplicemente un anonimo temporaneo, che è stato distrutto immediatamente, come sottolineato dai commenti sotto.std :: lock_guard che causa un comportamento indefinito

Edit2: L'attivazione del disinfettante per filetti clang aiuta a individuare questi tipi di problemi in fase di esecuzione. Può essere attivata tramite

clang++ -std=c++14 -stdlib=libc++ -fsanitize=thread *.cpp -pthread 

Questo è probabilmente in qualche modo una domanda duplicato, ma non ho trovato nulla, quindi se è davvero duplicare mi dispiace. Questa dovrebbe essere una domanda per principianti comunque.

stavo giocando intorno con una semplice classe "Counter", dicono in linea nel file di

Counter.hpp:

#ifndef CLASS_COUNTER_HPP_ 
#define CLASS_COUNTER_HPP_ 

#include <mutex> 
#include <string> 
#include <exception> 

class Counter 
{ 
    public: 
      explicit Counter(std::size_t v = 0) : value_{v} {} 

      std::size_t value() const noexcept { return value_; } 

//   void increment() { ++value_; }  // not an atomic operation : ++value_ equals value_ = value_ + 1 
               // --> 3 operations: read, add, assign 
      void increment() noexcept 
      { 
       mutex_.lock(); 
       ++value_; 
       mutex_.unlock(); 
      } 

//   void decrement() noexcept 
//   { 
//    mutex_.lock(); 
//    --value_;      // possible underflow 
//    mutex_.unlock(); 
//   } 

      void decrement() 
      { 
       std::lock_guard<std::mutex>{mutex_}; 
       if (value_ == 0) 
       { 
        std::string message{"New Value ("+std::to_string(value_-1)+") too low, must be at least 0"}; 
        throw std::logic_error{message}; 
       } 
       --value_; 
      } 

    private: 
      std::size_t value_; 
      std::mutex mutex_; 
}; 

#endif 

In main.cpp si suppone un'istanza di contatore per essere incrementato e decrementato contemporaneamente:

main.cpp:

#include <iostream> 
#include <iomanip> 
#include <array> 
#include <thread> 
#include <exception> 

#include "Counter.hpp" 

    int 
main() 
{ 
    Counter counter{}; 
    std::array<std::thread,4> threads; 
    auto operation = [&counter]() 
    { 
      for (std::size_t i = 0; i < 125; ++i) 
       counter.increment(); 
    }; 
//  std::for_each(begin(threads),end(threads),[&operation](auto& val) { val = std::thread{operation}; }); 
    std::cout << "Incrementing Counter (" << std::setw(3) << counter.value() << ") concurrently..."; 
    for (auto& t : threads) 
    { 
      t = std::thread{operation}; 
    } 

    for (auto& t : threads) 
      t.join(); 
    std::cout << " new value == " << counter.value() << '\n'; 

    auto second_operation = [&counter]() 
    { 
      for (std::size_t i = 0; i < 125; ++i) 
      { 
       try 
       { 
        counter.decrement(); 
       } 
       catch(const std::exception& e) 
       { 
        std::cerr << "\n***Exception while trying to decrement : " << e.what() << "***\n"; 
       } 
      } 
    }; 

    std::cout << "Decrementing Counter (" << std::setw(3) << counter.value() << ") concurrently..."; 
    for (auto& t : threads) 
      t = std::thread{second_operation}; 
    for (auto& t : threads) 
      t.join(); 
    std::cout << " new value == " << counter.value() << '\n'; 

    return 0; 

La gestione delle eccezioni sembra funzionare come dovrebbe e il modo in cui lo capisco è che std :: lock_guard dovrebbe garantire lo sblocco di un mutex una volta che lock_guard esce dall'ambito.

Tuttavia sembra essere più complicato di così. Mentre l'incremento ha come risultato un valore finale di "500", il decremento, che dovrebbe portare a "0", non funziona. Il risultato sarà qualcosa tra "0" e "16" o così.

Se il tempo è cambiato, ad esempio utilizzando valgrind, sembra funzionare correttamente ogni volta.

Sono stato in grado di individuare il problema con l'utilizzo di std :: lock_guard. Se io definisco la funzione di decremento() come questo:

 void decrement() noexcept 
     { 
      mutex_.lock(); 
      --value_;      // possible underflow 
      mutex_.unlock(); 
     } 

tutto funziona bene (a patto che non v'è alcuna underflow). Ma una volta che faccio una semplice modifica a:

 void decrement() noexcept 
     {  
      std::lock_guard<std::mutex>{mutex_}; 
      --value_;      // possible underflow 
     } 

il comportamento è come ho descritto sopra. Presumo di non aver capito il comportamento e di utilizzare i casi di std :: lock_guard. Lo apprezzerei davvero se tu potessi indicarmi la direzione giusta!

Il programma viene compilato tramite clang++ -std=c++14 -stdlib=libc++ *.cpp -pthread.

+0

@DarkFalcon Questo è ciò che si intende: viene generata un'eccezione il client prova a decrementare il valore se è già 0. Nel modo in cui un'istanza di classe viene inizializzata, può essere solo 0 o superiore (size_t deve essere senza segno in ogni caso), quindi se è 0, dovrebbe semplicemente rimanere così. Questa domanda non riguarda in realtà il tentativo di implementare un contatore perfetto in ogni caso, ma la comprensione di come funziona std :: unique_lock – mbw

+2

È possibile ottenere prestazioni migliori se invece di bloccare il mutex si impiega l'incremento/decremento atomico. – SergeyA

risposta

7

std::lock_guard<std::mutex>{mutex_}; Non crea locale. Crea un temporaneo che viene distrutto alla fine della dichiarazione. Questo significa che il tuo valore non è protetto dal lucchetto.La guardia di blocco deve essere un locale:

void decrement() noexcept 
{  
    std::lock_guard<std::mutex> guard {mutex_}; 
    --value_;      // possible underflow 
} 
+0

Questo lo spiega. Grazie mille per la veloce risposta! – mbw

5

Il problema è che la linea

std::lock_guard<std::mutex>{mutex_}; 

non crea una variabile, ma piuttosto crea un lock_guard oggetto temporaneo che viene nuovamente distrutto immediatamente. Quello che probabilmente significava scrivere era:.

std::lock_guard<std::mutex> guard{mutex_}; 

Questo crea una variabile di tipo lock_guard, chiamato guard, che viene distrutto quando lascia il campo di applicazione (cioè al termine della funzione In sostanza, si è dimenticato di assegnare un nome al

+0

Questo è davvero il problema, grazie per chiarire le cose velocemente! – mbw

Problemi correlati