栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

状态机无法切换状态了

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

状态机无法切换状态了

项目场景:

项目基于C++实现,用到了状态机在不同状态之间切换来执行不同的流程。


问题描述:

状态机在特定情况下无法再继续执行状态切换。
项目代码还是比较复杂的,所以这里只是摘取了关键代码段(当然看这个代码段可能很容易看出问题所在)。下面test里面的helloWorldFunc模拟实际的状态切换调用,从输出结果上"hello world"没有输出,所以并没有进入这个状态。

#include 
#include 
#include 

using namespace std;

struct transition {
    using ptr = std::unique_ptr;
    using executor = std::function;

    transition(const executor& executor) : execute(executor) {
    }

    static void run(const executor& executor) {
        transition::run(std::make_unique(executor));
    }

    static void run (transition::ptr transition) {
        thread_local transition::ptr current;

        if (current) {
	        // if in a transition already queue for execution after current is completed
	        current->next = std::move(transition);
	        return;
        }

        for (current = std::move(transition); current; current = std::move(current->next)) {
            current->execute();
        }
    }

    executor execute;
    ptr next;
};

int main() {
    auto helloWorldFunc = []() {
        cout << "hello world" << endl;
    };
    
    auto exceptionFunc = []() {
        throw std::exception();
    };
    
    try {
        transition::run(exceptionFunc);
    } catch (...) {
        cout << "exception thrown" << endl;
    }

    try {
        transition::run(helloWorldFunc);
    } catch (...) {
        cout << "exception thrown" << endl;
    }
    return 0;
}

原因分析:

注意看上面代码中用到了thread_local变量current来保留当前执行的状态,一般情况下状态执行完的话 current会释放,这样当切换下一个状态的时候可以正常执行。但是如果前一个状态transition::run(exceptionFunc)在执行current->execute()的时候如果抛出了异常,那么current无法被置为空,这样的话当执行transition::run(helloWorldFunc)的时候,这个状态会被添加到前一个状态的next里面,并且一直没有机会被执行,所以状态机再也无法切换状态了。


解决方案:

解决方案的话在执行current->execute()的时候加上异常捕获,当捕获到异常的时候将current置成空。

        try {
            for (current = std::move(transition); current; current = std::move(current->next)) {
                current->execute();
            }
        } catch (...) {
            current.reset();
        }
总结:
  1. 在实际的项目中看这种问题还是有难度的,实际的项目中执行transition::run的地方并没有加异常捕获,异常捕获的代码是加在一个thread pool里面的执行体里的,然后状态切换task被enqueue到这个thread pool来执行,幸好在捕获异常的地方输出了一行日志,不然我估计看不出来root cause。所以我们代码中如果加了异常捕获的话一定要加日志输出error信息方便看问题。
  2. 类似状态机这种可能会执行不同函数的场景,要多想想被执行的函数会不会throw exception,如果throw了exception要考虑好异常处理。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/932142.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号