2012-03-09 12 views
9

Mam klasę reprezentującą maszynę o stanie skończonym, która powinna działać w pętli wiecznej i sprawdzać jej bieżący stan. W każdym automatie stanowym ustawi go na następny stan i albo zapadnie w stan idle albo wykona jakąś pracę. Chciałbym umożliwić innym wątkom zmianę stanu maszyny podczas jej pracy. Spowoduje to oczekiwany stan wyścigu. Dodaję więc wspólną blokadę wyłączania/odblokowywania pętli maszyny oraz metodę publiczną, która pozwala innym wątkom zmienić bieżący stan maszyny.Obsługa wzajemnego wykluczania w C++ 11

class Robot 
{ 
public: 
    enum StateType {s1,s2,s3,idle,finish}; 
    void run(); 
    void move(); 
private: 
    StateType currentState; 
    StateType nextState; 
    StateType previousState; 
    std::mutex mutal_state; 
}; 

Realizacja:

void Robot::run() 
{ 
    this->currentState = s1; 
    while(true) 
    { 
     mutal_state.lock(); 
     switch(currentState) 
     { 
     case s1: 
      // do some useful stuff here... 
      currentState = idle; 
      nextState = s3; 
      break; 
     case s2: 
      // do some other useful stuff here... 
      currentState = idle; 
      nextState = finish; 
      break; 
     case s3: 
      // again, do some useful things... 
      currentState = idle; 
      nextState = s2; 
      break; 
     case idle: 
      // busy waiting... 
      std::cout << "I'm waiting" << std::endl; 
      break; 
     case finish: 
      std::cout << "Bye" << std::endl; 
      mutal_state.unlock(); 
      return; 
     } 
     mutal_state.unlock(); 
    } 
} 

A metoda, która pozwala przenieść inne wątki zmienić aktualny stan:

void Robot::move() 
{ 
    mutal_state.lock(); 
    previousState = currentState; // Booommm 
    currentState = nextState; 
    mutal_state.unlock(); 
} 

nie uda się znaleźć to, co robię źle! Program ulega awarii w pierwszej linii funkcji move(). Z drugiej strony, GDB nie działa z C++ 11 i kod śledzenia nie jest możliwe ...

UPDATE:

Odtwarzanie wokół kodu, widzę, że problem jest w funkcji ruchu. Kiedy program próbuje zablokować fragment kodu wewnątrz move(), ulega awarii. Na przykład, jeśli ruch jest tak:

void Robot::move() 
{ 
    std::cout << "MOVE IS CALLED" << std::endl; 
    mutal_state.lock(); 
    //previousState = currentState; 
    //std::cout << "MOVING" << std::endl; 
    //currentState = nextState; 
    mutal_state.unlock(); 
} 

wyjściowa wynosi:

s1 
I'm waiting 
I'm waiting 
MOVE IS CALLED1 
The program has unexpectedly finished. 

Ale kiedy move to prosta funkcja, nie robi nic:

void Robot::move() 
{ 
    std::cout << "MOVE IS CALLED" << std::endl; 
    //mutal_state.lock(); 
    //previousState = currentState; 
    //std::cout << "MOVING" << std::endl; 
    //currentState = nextState; 
    //mutal_state.unlock(); 
} 

Program biegnie równolegle.

+0

Czy próbowałeś debugowania za pomocą instrukcji drukowania? – FrustratedWithFormsDesigner

+0

@ FrustratedWithFormsDesigner: Tak. Wiem, że "eksplozja" ma miejsce tylko wtedy, gdy jakiś wątek próbuje wywołać "ruch". –

+0

Jakiej wersji kompilatora używasz? – ildjarn

Odpowiedz

2

Moje propozycje:

1) jeśli nie masz debugger, jak można być tak pewna, że ​​to pierwsza linia ruch, który ulega awarii? Zawsze kwestionujesz wszelkie założenia dotyczące kodu, chyba że masz mocne dowody, aby go poprzeć.

2) Spojrzałbym na interesujący kod w stanie s3, ponieważ wykona to pierwsze wywołanie. Do tego momentu kod w s3 nie został uruchomiony. Albo to albo usuń cały pasek kodu z zamieszczonego przykładu, aby temu zapobiec.

3) Kompilator może wykonywać kopie zmiennych w rejestrach, należy zadeklarować wszystkie stany jako niestabilne, więc wie, że nie należy optymalizować w ten sposób.

2

Nie mogę ci pomóc, dlaczego twój kod "wybucha", ale mogę założyć, że problem nie leży w kodzie, który napisałeś, ponieważ działa dobrze dla mnie.

Wyjście to będzie dla mnie:

I'm working 
... 
Bye 

Kod:

int main() { 

    Robot r; 

    auto async_moves = [&]() { // simulate some delayed interaction 
     std::this_thread::sleep_for(std::chrono::seconds(2)); //See note 
     for(auto i = 0; i != 3; ++i) 
      r.move(); 

    }; 

    auto handle = std::async(std::launch::async, async_moves); 

    r.run(); 

} 

(Uwaga: trzeba kompilować ze -D_GLIBCXX_USE_NANOSLEEP zakładając używasz gcc, patrz this pytanie.)

Zauważ, że powyższy kod - a może i twój - jest wciąż wulkany na problem, że stany mogą zostać unieważnione, jeśli move zostanie wywołane dwa lub więcej razy przed ponownym uruchomieniem pętli.
jak jeden z komentarzy już wymienionych, wolą używać lock_guards:

std::lock_guard<std::mutex> lock(mutal_state); 
+0

Próbowanie 'std :: lock_guard' nie pomogło. Jednak nie mogę uruchomić twojego kodu. Kończy się: 'zakończ wywołanie po rzuceniu instancji 'std :: system_error' co(): Operacja niedozwolona'' –

+0

@ sorush-r Musisz dodać opcję -pthread compiler. To naprawi ten błąd. – inf

+0

Dodałem już obie opcje '-pthread' i' -lpthread'. –

1

Jeśli używasz g ++ na Linux, trzeba połączyć z -lpthread w celu muteksy lub gwintowania rzeczy działały prawidłowo. Jeśli tego nie zrobisz, nie zawiedzie połączenia, ale zamiast tego zachowa się źle lub ulegnie awarii w czasie wykonywania ...

+0

Prawdopodobnie lepiej jest użyć opcji -pthread. Zobacz tutaj: http://stackoverflow.com/q/2127797/893693. Zakładam jednak, że tak czynił. – inf

+0

Używam opcji łącznika '-pthread' i' -lpthread'. Nie ma problemu z łączeniem. –

1

Odpowiadam na własne pytanie! Ponieważ znajduję problem i nie był związany z blokowaniem ani mutexem implementacji C++ 0x. Istnieje klasa ImageProcess, która powinna kontrolować stan Robot. Ma wskaźnik do rodzica typu Robot* i przy użyciu tego, będzie move jego rodzicem. W tym celu zaimplementowałem funkcję workhorse i start. start spawns std::tread i biegnie workhorse na nim:

void ImageProcess::start() 
{ 
    std::thread x(&ImageProcess::workhorse, *this); 
    x.detach(); 
} 

zdałem sobie sprawę, że this->parent w konia pociągowego jest wiszącym wskaźnik. Oczywiście wywołanie parent->move() powinno się zawiesić. Ale natychmiast się nie psuje! Zaskakująco program sterujący wchodzi w funkcję move(), a następnie próbuje zmienić previousState nieistniejącego obiektu Robot. (lub zablokować muteks nieistniejącego Robot).

Znalazłem, że podczas wywoływania wątku, takiego jak std::thread x(&ImageProcess::workhorse, *this); x.join() or x.detach(), kod nie jest już uruchomiony w obiekcie wywołującym. Aby przetestować wydrukowany adres this i &image w obu Robot::run() i ImageProcess::workhorse. Były różne. Dodałem też public boolean foo do ImageProcess i zmieniła swoją wartość true w Robot, a następnie wydrukowany w workhorse i run w workhorse wartości jest zawsze 0 ale w Robot jest 1.

Uważam, że to bardzo dziwne zachowanie. Nie wiem, czy jest to związane z modelem pamięci lub własności ImageProcess jakoś zmienił się po std::thread x(&ImageProcess::workhorse, *this) ...

robię ImageProcess klasa wzór fabryki (wszystko jest statyczna!). Teraz jest ok.