【问题标题】:Event-driven simulation class事件驱动模拟类
【发布时间】:2010-09-27 01:32:23
【问题描述】:

我正在完成 Bjarne Stroustrup 编写的 C++ 编程语言中的一些练习。第12章末尾的第11题让我很困惑:

(*5) 设计并实现一个用于编写事件驱动仿真的库。提示:。 ...类任务的对象应该能够保存其状态并恢复该状态,以便它可以作为协程运行。特定任务可以定义为从任务派生的类的对象。一个任务要执行的程序可能被定义为一个虚函数。 ...应该有一个调度程序实现虚拟时间的概念。 ...任务需要沟通。为此设计一个班级队列。 ...

我不确定这到底是什么要求。任务是一个单独的线程吗? (据我所知,没有系统调用就不可能创建一个新线程,而且由于这是一本关于 C++ 的书,我不相信这是意图。)没有中断,如何启动和停止正在运行功能?我认为这将涉及忙等待(也就是说,不断循环并检查条件),尽管我看不出如何将其应用于可能在一段时间内不会终止的函数(例如,如果它包含无限循环) .

编辑:请参阅下面我的帖子了解更多信息。

【问题讨论】:

    标签: c++ simulation event-driven


    【解决方案1】:

    这是我对“事件驱动模拟”的理解:

    • 控制器处理事件队列,安排事件在特定时间发生,然后执行队列中的顶部事件。
    • 事件在预定时间瞬间发生。例如,“移动”事件将更新模拟中实体的位置和状态,以使状态向量在当前模拟时间有效。 “感知”事件必须确保所有实体的状态都在当前时间,然后使用一些数学模型来评估当前实体对其他实体的感知能力。 (想想机器人在板上移动。)
    • 因此,时间是不连续的,从一个事件跳到另一个事件。将此与时间驱动的仿真进行对比,其中时间以离散的步长移动,所有实体的状态都会在每个时间步长更新(大多数 Simulink 模型)。
    • 然后事件可以按其自然速率发生。在模拟中以最佳速率重新计算所有数据通常没有意义。

    大多数生产事件驱动的模拟在单线程中运行。它们的本质可能很复杂,因此尝试同步多线程模拟往往会增加指数层的复杂性。话虽如此,有一个名为Distributive Interactive Simulation (DIS) 的多进程军事模拟标准,它使用预定义的 TCP 消息在进程之间传输数据。

    编辑:定义建模和仿真之间的区别很重要。模型是系统或过程的数学表示。模拟是从一个或多个在一段时间内执行的模型构建的。同样,事件驱动的模拟从一个事件跳到另一个事件,而时间驱动的模拟以恒定的时间步长进行。

    【讨论】:

    • 到目前为止,这个答案对我来说最有意义。但是,如上所述,它看起来不像是“事件”驱动的模拟,因为每个事件都计划在特定时间发生。
    • 时间是模拟中的“虚拟时间”,从优先级队列中弹出。模拟并未与挂钟时间同步。
    • @NXC,没错。模拟通常比实时运行快得多。这就是它们有用的原因。您可以通过改变参数进行实验,并在“现实世界”过程发生所需的相同时间内生成数千(数百万?)个数据点。
    • 当然,有些模拟比实时慢得多。这些通常是流体流动或热传递的科学模拟,可能需要数小时才能模拟 30 秒的“实时”。
    【解决方案2】:

    提示:.

    是对 early versions of CFront 附带的旧协作式多任务库的引用(您也可以在该页面下载)。

    如果您阅读论文“A Set of C++ Classes for Co-routine Style Programming”,事情会变得更有意义。


    补充一点:

    我还不够老,无法使用任务库。但是,我知道 C++ 是在 Stroustrup 在 Simula 中编写了一个模拟后设计的,该模拟具有许多与任务库相同的属性,所以我一直对此感到好奇。

    如果我要实现书中的练习,我可能会这样做(请注意,我没有测试过这段代码,甚至没有尝试编译它):

    class Scheduler {
        std::list<*ITask> tasks;
      public:
        void run()
        {
            while (1) // or at least until some message is sent to stop running
                for (std::list<*ITask>::iterator itor = tasks.begin()
                          , std::list<*ITask>::iterator end = tasks.end()
                        ; itor != end
                        ; ++itor)
                    (*itor)->run(); // yes, two dereferences
        }
    
        void add_task(ITask* task)
        {
            tasks.push_back(task);
        }
    };
    
    struct ITask {
        virtual ~ITask() { }
        virtual void run() = 0;
    };
    

    我知道人们会不同意我的某些选择。例如,为接口使用结构;但是结构具有从它们继承的行为默认是公共的(从类继承默认是私有的),并且我看不到从接口私有继承的任何价值,那么为什么不将公共继承设为默认值呢?

    这个想法是调用 ITask::run() 将阻塞调度程序,直到任务到达可以中断的点,此时任务将从 run 方法返回,并等待调度程序调用再次运行以继续。 “cooperative multitasking”中的“cooperative”是指“任务说什么时候可以中断”(“coroutine”通常表示“cooperative multitasking”)。一个简单的任务可能只在它的 run() 方法中做一件事,一个更复杂的任务可能会实现一个状态机,并且可能使用它的 run() 方法来确定对象当前处于什么状态,并根据它调用其他方法在那个状态。任务必须偶尔放弃控制才能使其工作,因为这就是“合作多任务”的定义。这也是所有现代操作系统不使用协作多任务处理的原因。

    此实现不 (1) 遵循公平调度(可能会在任务的 run() 方法中保持运行的时钟滴答总数,并跳过相对于其他任务使用过多时间的任务,直到其他任务“赶上up"),(2) 允许删除任务,甚至 (3) 允许停止调度程序。

    至于任务之间的通信,您可以考虑查看 Plan 9's libtaskRob Pike's newsqueak 以获得灵感(“Newsqueak 的 UNIX 实现”下载包括一篇论文“Newsqueak 的实现”,该论文讨论了在一个有趣的虚拟机器)。

    但我相信这是 Stroustrup 心目中的基本骨架。

    【讨论】:

      【解决方案3】:

      在我看来,该练习要求您实施协作式多任务调度程序。调度程序以虚拟时间运行(您定义/实现的任何级别的时间滴答),根据队列选择要运行的任务(请注意,您需要实现的描述)以及当前任务何时运行完成后,调度程序选择下一个并开始运行。

      【讨论】:

      • 差不多 - 事实上,它让我想起了操作系统课上的很多家庭作业。
      【解决方案4】:

      离散事件模拟的通用结构基于以时间值为键的优先级队列。在广义上它是这样的:

      虽然(不是结束条件): 从优先级队列中弹出下一个事件(时间最短的事件) 处理该事件,这可能会产生更多事件 如果生成新事件: 将其放置在其生成时间键控的优先级队列中

      协同例程将模型的视图从以事件为中心转变为以实体为中心。实体可以经历一些生命周期(例如接受作业、获取资源 X、处理作业、释放资源 X、将作业放入队列以进行下一步)。这更容易编程,因为抓取资源是使用类似信号量的同步原语处理的。作业和同步原语在后台生成事件并将它们排队。

      这给出了一个在概念上类似于操作系统中的进程的模型,并且当进程的输入或请求的共享资源可用时,调度程序唤醒进程。协程模型使仿真更容易理解,这对于模拟复杂系统很有用。

      【讨论】:

        【解决方案5】:

        (我不是 C++ 开发人员)

        大概意思是你需要创建一个Task类(如Event中),它主要由回调函数指针和预定时间组成,并且可以存储在Scheduler类中的一个列表中,依次基本上应该跟踪时间计数器并在时间到达时调用每个任务的函数。这些任务应该由模拟的对象创建。

        如果您在离散模拟方面需要帮助,请继续编辑问题。

        【讨论】:

        • 如何保存和恢复任务状态?
        • 很好地阅读了哈珀的回答,这可能意味着每个任务可以运行一段时间,但是您需要在给定的“滴答声”处分配所有活动任务之间的虚拟时间。跨度>
        • 如果不使用线程或单独的进程,您将如何做到这一点?
        • @krusty.ar,您的评论描述的是时间驱动的模拟。
        【解决方案6】:

        这是对 Titandecoy 对 SottieT812 回答的评论的回应。它对于评论来说太大了,所以我决定让它成为另一个答案。

        从某种意义上说,它是事件驱动的,模拟状态仅响应事件而改变。例如,假设您有两个事件导弹发射导弹撞击。当 launch 事件执行时,它会计算出影响的时间和地点,并在适当的时间安排 impact 事件。导弹的位置不是在发射和撞击之间计算的,尽管它可能会有一个方法可以被其他对象调用以获取特定时间的位置。

        这与时间驱动的模拟形成对比,在该模拟中,导弹(以及模拟中的所有其他对象)的确切位置是在每个时间步长之后计算的,比如 1 秒。

        根据模型的特性、所需答案的保真度以及许多其他因素,事件驱动或时间驱动的模拟可能会表现得更好。

        编辑:如果有人有兴趣了解更多信息,请查看来自Winter Simulation Conference 的论文

        【讨论】:

        • 另外,导弹的位置可以在“飞行”事件中明确计算。这是一个自我植入事件的示例,因为通常一个飞行事件会导致另一个飞行事件,直到导弹失败或击中某物。
        • 当然。有很多不同的做事方式。有数千人(10 人,100 人?)为国防承包商工作。
        【解决方案7】:

        有一本名为DEMOS(Simula 上的离散事件建模)的书和框架描述了一个基于协程的框架(同名的DEMOS)。尽管有 30 年左右的历史,DEMOS 实际上是一个相当不错的系统,Graham Birtwistle 是一个非常好的人。

        如果你在 C++ 上实现协同例程(想想 setjump/longjump),你应该看看这本书,了解一个非常非常优雅离散事件建模框架的描述。虽然已经 30 年了,但它有点永恒的经典,仍然有粉丝群。

        【讨论】:

          【解决方案8】:

          在“me.yahoo.com/...”链接的论文中描述了 task.h 类:

          1. 任务并行执行
          2. 任务可能会暂停并稍后恢复

          该库被描述为一种多道程序设计方法。

          是否可以在不使用线程或单独进程的情况下做到这一点?

          【讨论】:

          • 这取决于您所说的“并行”。对于真正的并行,您需要线程/进程。但是如果你正在实现一个调度器(你需要每个任务有一个 yield() 和 resume() 方法来暂停和重新开始处理,并且调度器调用这些方法)你可以接近并行。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-06-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-10-05
          • 1970-01-01
          • 2012-10-18
          相关资源
          最近更新 更多