Обработка взаимного исключения в C ++ 11 - PullRequest
9 голосов
/ 09 марта 2012

У меня есть класс, представляющий конечный автомат, который должен работать в цикле навсегда и проверять его текущее состояние.В каждом автомате будет установлено его следующее состояние, либо он перейдет в состояние idle, либо выполнит какую-то работу.Я хотел бы позволить другому потоку изменять состояние машины, пока она работает.Это приведет к состоянию гонки, как и ожидалось.Поэтому я добавляю петлю обертки блокировки / разблокировки взаимного исключения машины и открытый метод, который позволяет другим потокам изменять текущее состояние машины.

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;
};

Реализация:

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();
    }
}

ИМетод перемещения, который позволяет другим потокам изменять текущее состояние:

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

Мне не удается найти то, что я делаю неправильно!Сбой программы в первой строке функции move().С другой стороны, GDB не работает с C ++ 11, и трассировка кода невозможна ...

ОБНОВЛЕНИЕ:

Играя с кодом, я могувижу, что проблема в функции перемещения.Когда программа пытается заблокировать кусок кода внутри move(), происходит сбой.Например, если перемещение выглядит так:

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();
}

Вывод:

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

Но когда move простая функция, ничего не делать:

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();
}

Программа запускается одновременно.

Ответы [ 4 ]

2 голосов
/ 10 марта 2012

Я не могу помочь вам, почему ваш код "взрывается", однако я могу предположить, что проблема не в коде, который вы опубликовали, так как он работает нормально для меня.

Это выведет для меня:

I'm working
...
Bye

Код:

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();

} 

(Примечание: вы должны скомпилировать с -D_GLIBCXX_USE_NANOSLEEP, если вы используете gcc, см. этот вопрос.)

Обратите внимание, что приведенный выше код - и ваш, возможно, тоже - все еще уязвим к проблеме, что состояния могут стать недействительными, если move вызывается дважды или более до повторного запуска цикла.
Как один из комментариевуже упоминалось, предпочитают использовать lock_guards:

std::lock_guard<std::mutex> lock(mutal_state);
2 голосов
/ 10 марта 2012

Мои предложения:

1) если у вас нет отладчика, как вы можете быть уверены, что это первая строка хода, которая дает сбой?Это всегда вопрос о допущениях, которые вы сделали в отношении кода, если только у вас нет веских доказательств, подтверждающих его.

2) Я бы посмотрел на любой интересный код в состоянии s3, так как это первоеПризыв переехать будет выполнять.До этого момента код в s3 не запускался.Либо это, либо удалите всю кодовую строку, которая есть в опубликованном примере, чтобы исключить это.

3) Компилятор может делать копии переменных в регистрах, вы должны объявить все состояния как volatile, чтобы он не зналоптимизировать таким образом.

1 голос
/ 10 марта 2012

Я отвечаю на свой вопрос!Потому что я нахожу проблему, и она не была связана ни с блокировкой, ни с мьютексной реализацией C ++ 0x.Существует класс ImageProcess, который должен контролировать состояние Robot.Он имеет указатель на своего родителя типа Robot* и, используя его, move будет его родителем.Для этого я реализовал функции workhorse и start er.start порождает std::tread и запускает workhorse на нем:

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

Я понял, что this->parent в рабочей лошадке - это свисающий указатель.Очевидно, что вызов parent->move() должен привести к сбою.Но это не сбой сразу!Удивительно, но управление программой входит в функцию move(), а затем пытается изменить previousState несуществующего элемента Robot.(или заблокировать мьютекс несуществующего Robot).

Я обнаружил, что при вызове потока, подобного std::thread x(&ImageProcess::workhorse, *this); x.join() or x.detach(), код больше не выполняется в объекте вызывающего.Для проверки я напечатал адреса this и &image в Robot::run() и ImageProcess::workhorse.Были разные.Я также добавил общедоступное логическое значение foo в ImageProcess и изменил его значение на true в Robot, затем напечатал его в workhorse и run, в workhorse значение всегда 0, но вRobot - это 1.

Я считаю, что это очень странное поведение.Я не знаю, связано ли это с моделью памяти или владение ImageProcess каким-то образом изменилось после std::thread x(&ImageProcess::workhorse, *this) ...

Я делаю ImageProcess класс фабричного шаблона (все статично!).Теперь все в порядке.

1 голос
/ 10 марта 2012

Если вы используете g ++ в linux, вам нужно связать с -lpthread, чтобы мьютексы или потоки работали правильно.Если вы этого не сделаете, он не потерпит связь, но вместо этого будет плохо себя вести или аварийно завершится во время выполнения ...

...