Simpy离散事件仿真(7)——专题指南(3)——Events事件

1 Events事件

SimPy包含一组用于各种目的的事件类型。它们都继承simpy.events.Event。下面的列表显示了SimPy中内置的事件的层次结构:

events.Event
|
+— events.Timeout
|
+— events.Initialize
|
+— events.Process
|
+— events.Condition
|  |
|  +— events.AllOf
|  |
|  +— events.AnyOf
.
.
.

以上是一组基本事件。事件是可扩展的,例如,资源定义了其他事件。在本指南中,我们将重点介绍simpy.events模块中的事件。《资源指南》描述了各种资源事件。

 

2 事件基础知识

简单事件与延期、未来或承诺非常相似(如果不完全相同的话)。Event类的实例用于描述任何类型的事件。事件可以处于以下状态之一。

(1)可能发生(未触发),

(2)会发生(触发)或

(3)已经发生(处理)。

它们按这个顺序经历这些状态一次。事件也与时间紧密相连,时间推动事件的状态改变。

最初,事件没有被触发,它只是内存中的对象。

如果事件被触发,它将在给定的时间被调度并插入SimPy的事件队列中。属性Event.trigged变为True。

只要不处理事件,就可以向事件添加回调。回调接受事件作为参数,储在event.Callbacks列表中。

当SimPy将事件从事件队列中弹出并调用其所有回调时,该事件将被处理。现在已无法添加回调。属性Event.processed变为True。

事件也有值。可以在事件触发之前或触发时设置该值,并可以通过event.value检索该值,或者在进程中通过生成事件(value=yield event)进行检索。

 

3 向事件添加回调

“什么?回调?我从来没见过任何回调!“如果您已经努力完成了教程,您可能会这么想。

那是教程故意设计的。向事件添加回调的最常见方法是从进程函数(yield event)中生成回调。这将添加进程的_resume() 方法作为回调。这就是挂起事件时恢复进程的方式。

但是,您可以将

任何可调用对象(函数)添加到回调列表中,只要它接受事件实例作为参数:

import simpy

def my_callback(event):
    print('Called back from', event)

env = simpy.Environment()
event = env.event()
event.callbacks.append(my_callback)
event.callbacks

如果已处理某个事件,则其所有event.callbacks都已执行,并且属性设置为“None”。这是为了防止您添加更多回调-当然不会调用这些回调,因为事件已经发生。

不过,进程在这方面很聪明。如果您挂起一个已处理的事件,_resume()将立即使用该事件的值恢复您的进程(因为没有什么可等待的)。

 

4 触发事件

当事件被触发时,它们要么成功要么失败。例如,如果在计算结束时触发一个事件,并且一切正常,则该事件将成功。如果在计算期间发生异常,则事件将失败。

要触发事件并将其标记为成功,可以使用event.succeed(value=None)。您可以选择向它传递一个值(例如,计算结果)。

要触发事件并将其标记为失败,请调用event.fail(exception)并将异常实例传递给它(例如,在计算失败期间捕获的异常)。

还有一种触发事件的通用方法:event.trigger(event)。这将获取传递给它的事件的值和结果(成功或失败)。

这三个方法都返回它们绑定到的事件实例。这允许您执行诸如Event(env).succeed()之类的操作。

 

5 事件的示例用法

上面概述的简单机制在使用事件(甚至是基本事件)的方式上提供了很大的灵活性。

一个例子是事件可以共享。它们可以由进程创建,也可以在进程上下文之外创建。它们可以传递到其他进程并链接:

class School:
    def __init__(self, env):
        self.env = env
        self.class_ends = env.event()
        self.pupil_procs = [env.process(self.pupil()) for i in range(3)]
        self.bell_proc = env.process(self.bell())

    def bell(self):
        for i in range(2):
            yield self.env.timeout(45)
            self.class_ends.succeed()
            self.class_ends = self.env.event()
            print()

    def pupil(self):
        for i in range(2):
            print(r' \o/', end='')
            yield self.class_ends

school = School(env)
env.run()
 \o/ \o/ \o/
 \o/ \o/ \o/

这也可以像SimPy 2中已知的钝化/重新激活一样使用。学生们上课时钝化,铃响时又重新激活。

 

6 让时间通过Timeout流逝

要在仿真中让时间过去,有一个Timeout事件。Timeou有两个参数:延迟和可选值:timeout(delay,value=None)。它在创建期间触发自身,并在now + delay时调度自身。因此,不能再次调用success()和fail()方法,并且必须在创建Timeout时将事件值传递给它。

delay可以是任何类型的数字,通常是int或float,它支持比较和加法。

 

7 进程也是事件

SimPy进程(由Process或env.Process()创建)也具有作为事件的良好属性。

这意味着,一个过程可以产生另一个过程。当另一个进程结束时,它将被恢复。事件的值将是该进程的返回值:

def sub(env):
    yield env.timeout(1)
    return 23

def parent(env):
    ret = yield env.process(sub(env))
    return ret

env.run(env.process(parent(env)))
23

当一个进程被创建时,它调度一个初始化事件,该事件将在被触发时启动进程的执行,您通常不需要处理这种类型的事件。

如果不希望进程立即启动,但在某个延迟之后,可以使用simpy.util.start_delayed()。此方法返回在实际启动进程之前使用Timeout的帮助进程。

对上面的例子进行修改,但是有一个sub()的delay:

from simpy.util import start_delayed

def sub(env):
    yield env.timeout(1)
    return 23

def parent(env):
    sub_proc = yield start_delayed(env, sub(env), delay=3)
    ret = yield sub_proc
    return ret

env.run(env.process(parent(env)))
23

注意,辅助进程需要额外的yield。

 

8 同时等待多个事件

有时,您想同时等待多个事件。例如,您可能希望等待资源,但不要等待无限长的时间,或者可能想等到一系列事件发生。

因此,SimPy提供AnyOf和AllOf事件,这两个事件都是条件事件。

两者都将事件列表作为参数,并在触发任何(至少一个)或所有事件时触发。

from simpy.events import AnyOf, AllOf, Event
events = [Event(env) for i in range(3)]
a = AnyOf(env, events)  # 任一事件触发时就触发
b = AllOf(env, events)  # 所有事件触发时才触发

条件事件的值是一个有序字典,每个触发事件都有一个条目。对于AllOf,字典的大小将始终与事件列表的长度相同。AnyOf的值dict将至少有一个条目。在这两种情况下,事件实例都用作键,事件值就是值。

作为AllOf和AnyOf的简写,您还可以使用逻辑运算符&(and)和|(or):

def test_condition(env):
    t1, t2 = env.timeout(1, value='spam'), env.timeout(2, value='eggs')
    ret = yield t1 | t2
    assert ret == {t1: 'spam'}

    t1, t2 = env.timeout(1, value='spam'), env.timeout(2, value='eggs')
    ret = yield t1 & t2
    assert ret == {t1: 'spam', t2: 'eggs'}

    # You can also concatenate & and |
    e1, e2, e3 = [env.timeout(i) for i in range(3)]
    yield (e1 | e2) & e3
    assert all(e.processed for e in [e1, e2, e3])

proc = env.process(test_condition(env))
env.run()

条件结果的顺序与指定条件事件的顺序相同。这允许使用以下习惯用法方便地获取在and条件(包括AllOf)中指定的多个事件的值:

def fetch_values_of_multiple_events(env):
    t1, t2 = env.timeout(1, value='spam'), env.timeout(2, value='eggs')
    r1, r2 = (yield t1 & t2).values()
    assert r1 == 'spam' and r2 == 'eggs'

    proc = env.process(fetch_values_of_multiple_events(env))
    env.run()

 

 

 

 

你可能感兴趣的:(学习教程,离散事件仿真,python,仿真器)