SimPy 共享资源

杨镇源 于 2024-06-05 发布 浏览量

Shared Resources

共享资源是流程间交互的一种建模方式,资源形成了一个阻塞点,processes在此处排队以使用它们。

SimPy定义了三类资源:

资源的基本概念

所有资源都有一个相同的基本概念:资源本身是一种容量通常有限的容器。进程可以尝试将某些东西放入资源中,也可以尝试将一些东西取出。如果资源已满或为空,他们必须排队等待。

这是每种资源的大致外观:

BaseResource(capacity):
   put_queue
   get_queue

   put(): event
   get(): event

每个资源都有一个最大容量和两个队列:一个队列用于希望向其中放入内容的进程,另一个队列则用于希望取出内容的进程。put()get()方法都返回一个event,该事件是在相应操作成功时触发的。

资源系统是模块化和可扩展的。例如,资源可以使用专门的队列和事件类型。这允许他们使用排序的队列,为事件添加优先级,或提供抢占权。

一、 Resources

Resources可以由有限数量的进程使用一段时间,Processes request 这些Resources以变成Resources的使用者(拥有者),然后在使用完毕后release这些Resources

Request a resource被建模为“putting a process’ token into the resource”,相应地release a resource被建模为getting a process’ token out of the resource”。因此,调用request()/release()等同于调用put()/get()。Release a resource总会立刻成功。

Simpy提供了三种resource类型:

1.1 Resource

Resource概念上等同于信号量,它唯一的参数(除了对env的强制性引用之外)是它的容量。它必须是一个正数,并且默认为1:Resource(env,capacity=1)

基本用法:

import simpy

def resource_user(env, resource):
    request = resource.request()  # Generate a request event
    yield request                 # Wait for access
    yield env.timeout(1)          # Do something
    resource.release(request)     # Release the resource

env = simpy.Environment()
res = simpy.Resource(env, capacity=1)
user = env.process(resource_user(env, res))
env.run()

注意,无论何时请记得释放资源,例如,如果您在等待或使用资源时被中断。为了帮助你做到这一点,并避免过多使用:try:... finally:...,request events可以被用作context manager:

def resource_user(env, resource):
    with resource.request() as req:  # Generate a request event
        yield req                    # Wait for access
        yield env.timeout(1)         # Do something
                                     # Resource released automatically
user = env.process(resource_user(env, res))
env.run()

通过res.countres.capacity可以获得当前用户的数量和资源的容量;通过res.usersres.queue可以获得当前使用资源的用户列表和等待资源的用户列表

1.2 PriorityResource

Resource的这个子类允许requesting进程为每个请求提供优先级。更重要的请求将比不太重要的请求更早地访问资源。优先级用整数表示;数字越小,优先级越高。

def resource_user(name, env, resource, wait, prio):
    yield env.timeout(wait)
    with resource.request(priority=prio) as req:
        print(f'{name} requesting at {env.now} with priority={prio}')
        yield req
        print(f'{name} got resource at {env.now}')
        yield env.timeout(3)

env = simpy.Environment()
res = simpy.PriorityResource(env, capacity=1)
p1 = env.process(resource_user(1, env, res, wait=0, prio=0))
p2 = env.process(resource_user(2, env, res, wait=1, prio=0))
p3 = env.process(resource_user(3, env, res, wait=2, prio=-1))
env.run()
# Output:
1 requesting at 0 with priority=0
1 got resource at 0
2 requesting at 1 with priority=0
3 requesting at 2 with priority=-1
3 got resource at 3
2 got resource at 6

1.3 PreemptiveResource

抢占式:

def resource_user(name, env, resource, wait, prio):
    yield env.timeout(wait)
    with resource.request(priority=prio) as req:
        print(f'{name} requesting at {env.now} with priority={prio}')
        yield req
        print(f'{name} got resource at {env.now}')
        try:
            yield env.timeout(3)
        except simpy.Interrupt as interrupt:
            by = interrupt.cause.by
            usage = env.now - interrupt.cause.usage_since
            print(f'{name} got preempted by {by} at {env.now}'
                  f' after {usage}')

env = simpy.Environment()
res = simpy.PreemptiveResource(env, capacity=1)
p1 = env.process(resource_user(1, env, res, wait=0, prio=0))
p2 = env.process(resource_user(2, env, res, wait=1, prio=0))
p3 = env.process(resource_user(3, env, res, wait=2, prio=-1))
env.run()
# Output:
1 requesting at 0 with priority=0
1 got resource at 0
2 requesting at 1 with priority=0
3 requesting at 2 with priority=-1
1 got preempted by <Process(resource_user) object at 0x...> at 2 after 2
3 got resource at 2
2 got resource at 5

PreemptiveResource继承自PriorityResource,并且添加了一preempt标志(默认为True),如果手动将其设置为False将令其不抢占其他资源占用者。

同时值得注意的是,PreemptiveResource的优先级先于抢占,这意味着抢占请求不允许“欺骗”和“跳过”优先级更高的请求。以下示例显示了抢占式低优先级请求无法在高优先级请求上进行队列跳跃:

def user(name, env, res, prio, preempt):
    with res.request(priority=prio, preempt=preempt) as req:
        try:
            print(f'{name} requesting at {env.now}')
            assert isinstance(env.now, int), type(env.now)
            yield req
            assert isinstance(env.now, int), type(env.now)
            print(f'{name} got resource at {env.now}')
            yield env.timeout(3)
        except simpy.Interrupt:
            print(f'{name} got preempted at {env.now}')

env = simpy.Environment()
res = simpy.PreemptiveResource(env, capacity=1)
A = env.process(user('A', env, res, prio=0, preempt=True))
env.run(until=1)  # Give A a head start
# Output:
A requesting at 0
A got resource at 0

B = env.process(user('B', env, res, prio=-2, preempt=False))
C = env.process(user('C', env, res, prio=-1, preempt=True))
env.run()
# Output:
# 进程C请求优先级为-1的资源,保留抢占符号为True
# 通常它会抢占A,但在这种情况下,B在C之前排队,并阻止C抢占A。C也不能抢占B,因为它的优先级不够高
B requesting at 1
C requesting at 1
B got resource at 3
C got resource at 6

该示例中的行为与根本没有使用抢占的情况相同。使用混合抢占时要小心!

二、Containers

容器可以帮助您对均质、无差别散装产品的生产和消费进行建模。它可能是连续的(像水一样),也可能是离散的(像苹果一样)。例如,您可以使用它来对加油站的油箱/汽油箱进行建模。油轮增加了油箱中的汽油量,而汽车则减少了汽油量。

以下示例是一个非常简单的加油站模型,该加油站具有有限数量的加油机(建模为Resource)和建模为Container的油箱:

class GasStation:
    def __init__(self, env):
        self.fuel_dispensers = simpy.Resource(env, capacity=2)
        self.gas_tank = simpy.Container(env, init=100, capacity=1000)
        self.mon_proc = env.process(self.monitor_tank(env))

    def monitor_tank(self, env):
        while True:
            if self.gas_tank.level < 100:
                print(f'Calling tanker at {env.now}')
                env.process(tanker(env, self))
            yield env.timeout(15)


def tanker(env, gas_station):
    yield env.timeout(10)  # Need 10 Minutes to arrive
    print(f'Tanker arriving at {env.now}')
    amount = gas_station.gas_tank.capacity - gas_station.gas_tank.level
    yield gas_station.gas_tank.put(amount)


def car(name, env, gas_station):
    print(f'Car {name} arriving at {env.now}')
    with gas_station.fuel_dispensers.request() as req:
        yield req
        print(f'Car {name} starts refueling at {env.now}')
        yield gas_station.gas_tank.get(40)
        yield env.timeout(5)
        print(f'Car {name} done refueling at {env.now}')


def car_generator(env, gas_station):
    for i in range(4):
        env.process(car(i, env, gas_station))
        yield env.timeout(5)


env = simpy.Environment()
gas_station = GasStation(env)
car_gen = env.process(car_generator(env, gas_station))
env.run(35)
Car 0 arriving at 0
Car 0 starts refueling at 0
Car 1 arriving at 5
Car 0 done refueling at 5
Car 1 starts refueling at 5
Car 2 arriving at 10
Car 1 done refueling at 10
Car 2 starts refueling at 10
Calling tanker at 15
Car 3 arriving at 15
Car 3 starts refueling at 15
Tanker arriving at 25
Car 2 done refueling at 30
Car 3 done refueling at 30

容器允许您检索其当前液位和容量(请参阅GasStation.monitor_tank()tanker())。您还可以通过put_queueget_queue属性(类似于Resource.queue)访问等待事件的列表。

三、Stores

使用Stores,您可以对具体对象的生产和消费进行建模(与Containers中存储的相当抽象的“数量”形成对比)。单个Store甚至可以包含多种类型的对象。除了Store,还有一个FilterStore,它允许您使用自定义函数来过滤从Store中取出的对象,以及PriorityStore,其中items按优先级顺序从Store中取出来。

def producer(env, store):
    for i in range(100):
        yield env.timeout(2)
        yield store.put(f'spam {i}')
        print(f'Produced spam at', env.now)


def consumer(name, env, store):
    while True:
        yield env.timeout(1)
        print(name, 'requesting spam at', env.now)
        item = yield store.get()
        print(name, 'got', item, 'at', env.now)


env = simpy.Environment()
store = simpy.Store(env, capacity=2)

prod = env.process(producer(env, store))
consumers = [env.process(consumer(i, env, store)) for i in range(2)]

env.run(until=5)
# Output:
0 requesting spam at 1
1 requesting spam at 1
Produced spam at 2
0 got spam 0 at 2
0 requesting spam at 3
Produced spam at 4
1 got spam 1 at 4