【问题标题】:Handling mutual exclusion in C++11在 C++11 中处理互斥
【发布时间】:2012-03-27 06:11:44
【问题描述】:

我有一个代表有限状态机的类,它应该在一个永远循环中运行并检查它的当前状态。在每个状态机将设置它的下一个状态,要么进入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();
    }
}

以及允许其他线程改变当前状态的move方法:

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

程序同时运行。

【问题讨论】:

  • 你试过用打印语句调试吗?
  • @FrustratedWithFormsDesigner:是的。我知道“爆炸”只会在某个线程尝试调用move 时发生。
  • 你使用什么编译器版本?
  • @ildjarn:我使用的是 gcc 版本 4.6.1 (Ubuntu/Linaro 4.6.1-9ubuntu3),线程模型是 posix
  • 您最好开始使用锁防护装置(例如std::lock_guard&lt;std::mutex&gt;std::unique_lock&lt;std::mutex&gt;),而不是手动锁定和解锁。这可能对您的直接问题没有帮助,但只能帮助维护/静态验证。更不用说异常安全了。

标签: c++ multithreading c++11 mutual-exclusion


【解决方案1】:

我的建议:

1) 如果你没有调试器,你怎么能确定是第一行移动崩溃了?除非你有确凿的证据支持它,否则总是质疑你对代码所做的任何假设。

2) 我会查看状态 s3 中的任何有趣代码,因为这是第一次调用 move 将执行的操作。到目前为止,s3 中的代码尚未运行。要么删除所有代码栏,要么删除发布示例中的所有代码栏,以排除这种情况。

3) 编译器可能会复制寄存器中的变量,您应该将所有状态声明为 volatile,这样它就知道不要以这种方式优化。

【讨论】:

    【解决方案2】:

    我无法帮助您解释为什么您的代码“爆炸”,但是我可以假设问题不在您发布的代码中,因为它对我来说运行良好。

    这将为我输出:

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

    (注意:如果您使用 gcc,则必须使用 -D_GLIBCXX_USE_NANOSLEEP 进行编译,请参阅 this 问题。)

    请注意,上面的代码 - 可能还有你的代码 - 仍然容易受到问题的影响,如果在循环再次触发之前调用 move 两次或更多次,状态可能会失效。
    就像已经提到的其中一个 cmets 一样,更喜欢使用 lock_guards:

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

    【讨论】:

    • 尝试std::lock_guard 没有帮助。但是我无法运行您的代码。它终止:terminate called after throwing an instance of 'std::system_error' what(): Operation not permitted'
    • @sorush-r 您必须添加 -pthread 编译器选项。这将解决该错误。
    • @sorush-r 对我来说没有意义,因为这正是我在没有-pthread 的情况下得到的错误,我正在像这样编译g++ -std=c++0x -pthread -D_GLIBCXX_USE_NANOSLEEP -o main main.cpp
    • 糟糕!抱歉,我使用的是 Qt 构建系统,在将 -lpthread 添加到 .pro 文件后忘记执行 qmake。谢谢你的回答:)
    【解决方案3】:

    如果您在 linux 上使用 g++,则需要链接 -lpthread 以使互斥锁或线程工作正常工作。如果不这样做,它不会链接失败,而是会在运行时表现不佳或崩溃...

    【讨论】:

    • 最好使用 -pthread 选项。见这里:stackoverflow.com/q/2127797/893693。但是,我认为他还是这样做了。
    • 我同时使用-pthread-lpthread 链接器选项。链接没有问题。
    【解决方案4】:

    我正在回答我自己的问题!因为我发现了问题,而且它与 C++0x 的锁定和互斥实现无关。有一个ImageProcess 类应该控制Robot 的状态。它有一个指向它的父类型Robot* 的指针,使用它,move 它的父。为此,我实现了workhorsestarter 函数。 start 生成 std::tread 并在其上运行 workhorse

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

    我意识到主力中的this-&gt;parent 是一个悬空指针。显然调用parent-&gt;move() 应该会崩溃。但它不会立即崩溃!令人惊讶的是,程序控制进入move() 函数,然后尝试更改不存在的Robot 事物的previousState。 (或锁定不存在的Robot 的互斥锁)。

    我发现当调用像std::thread x(&amp;ImageProcess::workhorse, *this); x.join() or x.detach() 这样的线程时,代码不再在调用者对象中运行。为了测试,我在Robot::run()ImageProcess::workhorse 中都打印了this&amp;image 的地址。有不同的。我还添加了一个公共布尔值fooImageProcess 并将其值更改为Robot 中的true,然后在workhorserun 中打印它,在workhorse 中值始终是0 但在Robot1

    我相信这是非常奇怪的行为。我不知道它是否与内存模型或ImageProcess 的所有权有关,在std::thread x(&amp;ImageProcess::workhorse, *this) 之后以某种方式更改...

    我将ImageProcess 设为工厂模式类(一切都是静态的!)。现在好了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-14
      • 2015-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多