共享资源是进程交互的另一种方式。它们形成了一个拥塞点,进程排队使用它们。
SimPy定义了三类资源:
(1)Resources——一次可由有限数量的过程使用的资源(例如,具有有限数量燃油泵的加油站)。
(2)Containers——模拟同质、未分散的生产、消费资源。它可以是连续的(如水)或离散的(如苹果)。
(3)Stores——允许生产和使用Python对象的资源。
1 资源的基本概念
所有资源都有相同的基本概念:资源本身是某种容量的容器,通常是有限容量。进程可以尝试将某些内容放入资源中,也可以尝试将某些内容取出。如果资源已满或为空,则必须排队等待。
以下大致就是资源的构成:
BaseResource(capacity):
put_queue
get_queue
put(): event
get(): event
每个资源都有一个最大容量和两个队列:一个用于希望将某些内容放入其中的进程,另一个用于希望将某些内容取出的进程。put()和get()方法都返回一个事件,该事件在相应操作成功时触发。
当进程等待put或get事件成功时,它可能会被另一个进程中断。在捕捉到中断后,该进程有两种可能:
(1)它可以继续等待请求(再次生成事件)。
(2)它可能会停止等待请求。在这种情况下,必须调用事件的cancel()方法。
因为您很容易忘记这一点,所以所有资源事件都是上下文管理器(有关详细信息,请参阅Python文档)。
资源系统具有模块化和可扩展性。例如,资源可以使用专用队列和事件类型,允许使用排序队列,向事件添加优先级,或提供抢占。
2 Resources
资源可以同时由有限数量的进程使用(例如,具有有限数量燃油泵的加油站)。进程要求使用这些资源(或“拥有”这些资源),并且必须在完成后释放资源(例如,车辆到达加油站,使用燃油泵(如果可用),完成后离开)。
请求资源的建模行为是“将进程的令牌放入资源”,相应的,释放资源是“从资源中取出进程的令牌”。因此,调用request()/release()等同于调用put()/get()。释放资源总是会立即成功。
SimPy实现了三种Rescource类型:
(1)Resource,资源
(2)PriorityResource,有限资源,其中排队进程按优先级排序
(3)PreemptiveResource,抢占资源其中进程可以另外抢占优先级较低的其他进程
2.1 资源Resource
Resource在概念上是一个信号量。它唯一的参数——除了必须提到的环境之外——是它的容量。它必须是正数,默认为1:Resource(env,capacity=1)。
它不只是计算当前用户,而是将请求事件存储为每个用户的“访问令牌”。这对于添加抢占非常有用(见下文)。
下面是使用Resource的基本示例:
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:...构造、请求事件可以使用上下文管理器:
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 = simpy.Resource(env, capacity=1)
def print_stats(res):
print(f'{res.count} of {res.capacity} slots are allocated.')
print(f' Users: {res.users}')
print(f' Queued events: {res.queue}')
def user(res):
print_stats(res)
with res.request() as req:
yield req
print_stats(res)
print_stats(res)
procs = [env.process(user(res)), env.process(user(res))]
env.run()
0 of 1 slots are allocated. Users: [] Queued events: [] 1 of 1 slots are allocated. Users: [<Request() object at 0x...>] Queued events: [] 1 of 1 slots are allocated. Users: [<Request() object at 0x...>] Queued events: [<Request() object at 0x...>] 0 of 1 slots are allocated. Users: [] Queued events: [<Request() object at 0x...>] 1 of 1 slots are allocated. Users: [<Request() object at 0x...>] Queued events: [] 0 of 1 slots are allocated. Users: [] Queued events: []
2.2 PriorityResource优先资源
从现实世界中你可能知道,并不是每个人都同等重要。把它映射到SimPy,这是PriorityResource。此资源子类允许请求进程为每个请求提供优先级。更重要的请求将比不重要的请求更早地访问资源。优先级用整数表示;较小的数字意味着较高的优先级。
除此之外,它的工作方式与普通资源类似:
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()
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
尽管p3请求的资源比p2晚,但它可以更早地使用它,因为它的优先级更高。
2.3 PreemptiveResource抢占资源
有时,新的请求非常重要,以至于队列跳转还不够,它们需要将现有用户踢出资源(这称为抢占)。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()
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继承,并向request()添加一个preempt标志(默认为True)。通过将其设置为False(resource.request(priority=x,preempt=False)),进程可以决定不抢占另一个资源用户。不过,它仍将根据优先级放入队列中。
抢占资源的实现优先权高于抢占。这意味着抢占请求不允许欺骗和跳过优先级更高的请求。以下示例显示,抢占式低优先级请求无法在高优先级请求上排队跳转:
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
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()
B requesting at 1 C requesting at 1 B got resource at 3 C got resource at 6
(1)处理A请求优先级为0的资源。它立即成为用户。
(2)进程B请求优先级为-2的资源,但将preempt设置为False。它会排队等候。
(3)进程C请求优先级为-1的资源,但保留preempt为True。通常情况下,它会抢占A,但在这种情况下,B在C之前排队,并阻止C抢占A。由于B的优先级不够高,C也不能抢占B。
因此,示例中的行为与完全不使用抢占一样。使用混合抢占时要小心!
由于进程B的优先级较高,在本例中不发生抢占。注意,优先级为-3的附加请求可以抢占A。
如果您的用例需要不同的行为,例如队列跳转或将抢占值置于优先级之上,则可以将抢占资源子类化并重写默认行为。