【SimPy系列博客之官方example学习与解读】—— Example 1: Bank Renege

2024-01-09 00:52

本文主要是介绍【SimPy系列博客之官方example学习与解读】—— Example 1: Bank Renege,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Hello,CSDN的各位小伙伴们,拖更了几年后,又重新开始记录这段时间准备学习的东西啦!最近博主在学习 SimPy 的相关知识,发现国内关于 SimPy 的介绍还是相对较少,且缺乏系统性,因此博主打算用1~2周的时间,边学边记,打算从 SimPy 官方给出的几个例子出发分析 SimPy 的使用场景和用法,希望能和大家互勉!那废话不多说,我们直接开始吧!今天要学习的例程是 Bank Renege

文章目录

  • 例程背景
  • 例程代码分析
  • 结果分析
  • 扩展

例程背景

这个example最初的流程是这样的:有一个银行,银行里面只有一个柜台 (Counter),众多顾客以一个随机时间到达银行,柜台一次只能服务一个用户,并会消耗一定的服务时间。抵达银行的顾客如果发现柜台有人,则需要排队等候。而每一个顾客的等待时间是有限制的,如果超过一定的时间后还没有轮到自己,那么该顾客就会离开。我们的目的就是用 SimPy 对这个流程进行建模。

例程代码分析

首先自然是导入一些必要的模块,这里不再赘述

import simpy
import random

接下来我们定义一些仿真相关的参数:

RANDOM_SEED = 2024  # 随机种子
NUM_OF_CUSTOMERS = 5  # 仿真过程中顾客的总数量
INTERVAL_CUSTOMERS = 10.0  # 每个顾客的到达时间大致相差10s
MIN_PATIENCE = 1  # 顾客的最小允许等待时间
MAX_PATIENCE = 3  # 顾客的最大允许等待时间

下面是整个仿真的关键部分:我们首先来定义单个用户的行为。即在用户到达银行后,他的一些行为。用户到达银行后,先会看counter资源是否空闲,如果空闲,则直接开始使用柜台一段时间用完柜台后释放柜台资源。如果柜台正在被占用,那么用户就会等待,如果在允许的时间内等到了空闲的counter,那么就开始使用;否则用户将离开

我们来看看这部分代码怎么写的:

def customer(env, name, counter, time_in_bank):"""定义用户的行为 (排队等待,使用柜台,离开等):param env: 仿真环境:param name: 用户的标识号:param counter: 柜台资源:param time_in_bank: 用户使用柜台的时间:return:"""arrival_time = env.now  # 用户抵达银行的时间print('Customer: ', name, ' arrive the bank at: ', arrival_time)with counter.request() as req:patience = random.uniform(MIN_PATIENCE, MAX_PATIENCE)  # 定义该用户的允许等待时间results = yield req | env.timeout(patience)waiting_time = env.now - arrival_time  # 该用户的等待时间if req in results:# 说明用户等到了counterprint('Customer: ', name, ' is ready to use the counter at: ', env.now)tib = random.expovariate(1.0 / time_in_bank)  # 用户使用柜台的时间yield env.timeout(tib)print('Customer: ', name, ' finished at: ', env.now)else:# 说明等待时间超过了用户的最大允许等待时间print('Customer: ', name, ' has waited for: ', waiting_time, ' cannot wait any longer!')

其中,env是由simpy创建的仿真环境的名字,env.now可以获取到当前仿真的时间。另外,我们看到这句话:with counter.request() as req:因为 counter是仿真中的一个资源 (Resource) ,resource在simpy中的定义就是在某个时刻只能被一定数量的processes使用,我们在这里定义的customer函数就是一个process。当process使用resource时,它就变成了该resource的owner,当process使用完resource时,就必须将resource释放,以便后面其他process可以继续使用该resource。

而代码中的with…as…的语句,则可以实现process对resource的请求和释放。在with的缩进里面,我们可以写一些代码,用来表示执行一些需要使用资源的操作,一旦跳出了with的缩进,那么resource就会被自动释放

那么对于后面的 yield req语句,它的意思就是:当程序执行到这句话时,customer这个函数就会暂停,并等待,直到resource被分配到这个用户。一旦resource被分配到了这个用户,那么代码将继续从yield req后面的语句开始继续执行

接下来,我们再看:yield req | env.timeout(patience)符号 "|"在simPy中的意思是在多个事件中进行选择,会选择最先发生的那个事件

至此,我们对单个用户的行为建模已经有所了解,下面我们再来看看如何建模用户到达bank的过程,这就包括:用户什么时候到达bank, 再根据我们上面的代码决定用户到达bank后要干啥。

def customers_arrival(env, num_of_customers, interval_customers, counter):"""定义用户抵达银行的这个随机过程:param env: simpy的仿真环境:param num_of_customers: 用户的总数量:param interval_customers: 用户抵达银行的大致时间间隔:param counter: 银行柜台 (是simpy中的resource):return:"""for i in range(num_of_customers):c = customer(env, str(i), counter, time_in_bank=12.0)env.process(c)t = random.expovariate(1.0 / interval_customers)yield env.timeout(t)

可以看到,对于每一个用户,我们首先定义了它个体到达银行后的行为,c = customer(env, str(i), counter, time_in_bank=12.0),将其加入到仿真环境中,然后再模拟下一个用户到达的时间。

在所有相关process都定义好后,就正式进入仿真,即我们需要定义counter资源,然后把上面定义的一些process加入到仿真环境中:

random.seed(RANDOM_SEED)
env = simpy.Environment()  # 创建仿真环境counter = simpy.Resource(env, capacity=1)  # counter是整个仿真环境的资源,且只有一个柜台
env.process(customers_arrival(env, NUM_OF_CUSTOMERS, INTERVAL_CUSTOMERS, counter))
env.run()

仿真输出的结果为:

Customer:  0  arrive the bank at:  0
Customer:  0  is ready to use the counter at:  0
Customer:  0  finished at:  4.344581265041675
Customer:  1  arrive the bank at:  6.350494539377634
Customer:  1  is ready to use the counter at:  6.350494539377634
Customer:  1  finished at:  21.48185544267454
Customer:  2  arrive the bank at:  28.180599526311216
Customer:  2  is ready to use the counter at:  28.180599526311216
Customer:  3  arrive the bank at:  31.262456454262153
Customer:  3  has waited for:  1.8317442018885792  cannot wait any longer!
Customer:  4  arrive the bank at:  38.16001347598838
Customer:  4  has waited for:  2.9264992955047973  cannot wait any longer!
Customer:  2  finished at:  48.273541968295305

结果分析

我们可以看到,当第一个用户抵达bank时,counter空闲,因此用户0可以直接用,在4.344min用完counter并释放资源。值得注意的是:用户2使用counter的时间比较长,大概花了20min,而在此期间,用户3在第31.26min抵达bank,不巧用户3的最大允许等待时间比较短,因此,在等待了1.83min后,用户3离开。

扩展

我们希望通过对example的学习能够理解simpy其中的一些逻辑,因此,除了看懂example,我们还需要能够灵活改变源码以满足自己的想法。

如果我们想更好的模块化编程,那么用户就是一个单独的类,我们先考虑只有一个用户的情况

class Customer():def __init__(self, env, name, counter, time_in_bank):self.env = envself.name = nameself.counter = counterself.time_in_bank = time_in_bankself.action = env.process(self.waiting())def waiting(self):arrival_time = self.env.now  # 用户抵达银行的时间print('Customer: ', self.name, ' arrive the bank at: ', arrival_time)with counter.request() as req:patience = random.uniform(MIN_PATIENCE, MAX_PATIENCE)  # 定义该用户的允许等待时间results = yield req | env.timeout(patience)waiting_time = env.now - arrival_time  # 该用户的等待时间if req in results:# 说明用户等到了counterprint('Customer: ', self.name, ' is ready to use the counter at: ', env.now)tib = random.expovariate(1.0 / self.time_in_bank)  # 用户使用柜台的时间yield env.timeout(tib)print('Customer: ', self.name, ' finished at: ', env.now)else:# 说明等待时间超过了用户的最大允许等待时间print('Customer: ', self.name, ' has waited for: ', waiting_time, ' cannot wait any longer!')

其中,用户类里面的waiting方法就是我们原本的process,但是我们加了一句话:self.action = env.process(self.waiting()),相当于是把waiting方法加入到仿真中了。这样,我们在定义好这个类时,process就会在对应的时间自动被触发:

env = simpy.Environment()
counter = simpy.Resource(env, capacity=1)  # counter是整个仿真环境的资源,且只有一个柜台
C0 = Customer(env, name='C0', counter=counter, time_in_bank=12.0)
env.run()

输出如下:

Customer:  C0  arrive the bank at:  0
Customer:  C0  is ready to use the counter at:  0
Customer:  C0  finished at:  1.7192205283488458

那么,继续,如果我们想要用很多用户,代码可以这样写:

def customers_arrival(env, num_of_customers, interval_customers, counter):"""定义用户抵达银行的这个随机过程:param env: simpy的仿真环境:param num_of_customers: 用户的总数量:param interval_customers: 用户抵达银行的大致时间间隔:param counter: 银行柜台 (是simpy中的resource):return:"""for i in range(num_of_customers):Customer(env, name=str(i), counter=counter, time_in_bank=12.0)t = random.expovariate(1.0 / interval_customers)yield env.timeout(t)

因为我们在Customer类里面已经把waiting方法加入到env中了,所以此时我们只需要把Customer实例化,就能够自动执行process。因此,我们通过下面的代码启动仿真:

env = simpy.Environment()
counter = simpy.Resource(env, capacity=1)  # counter是整个仿真环境的资源,且只有一个柜台
env.process(customers_arrival(env, NUM_OF_CUSTOMERS, INTERVAL_CUSTOMERS, counter))
env.run()

结果如下:

Customer:  0  arrive the bank at:  0
Customer:  0  is ready to use the counter at:  0
Customer:  0  finished at:  2.185043045116575
Customer:  1  arrive the bank at:  27.834642847492574
Customer:  1  is ready to use the counter at:  27.834642847492574
Customer:  1  finished at:  30.715420426177218
Customer:  2  arrive the bank at:  47.63187135568359
Customer:  2  is ready to use the counter at:  47.63187135568359
Customer:  3  arrive the bank at:  52.263879692999126
Customer:  3  has waited for:  2.0577798944440318  cannot wait any longer!
Customer:  4  arrive the bank at:  69.6644022591966
Customer:  4  has waited for:  2.1264195985524026  cannot wait any longer!
Customer:  2  finished at:  86.37400774992372

那么。进一步地,如果我们希望很多用户同时到达bank,要怎么做呢?有两种方法:
(1)不执行customers_arrival函数,直接实例化多个Customer类

env = simpy.Environment()
counter = simpy.Resource(env, capacity=1)  # counter是整个仿真环境的资源,且只有一个柜台customers = []
for i in range(NUM_OF_CUSTOMERS):customers.append(Customer(env, name=str(i), counter=counter, time_in_bank=12.0))
env.run()

(2)修改customers_arrival函数,在遍历所有用户时,将yield的timeout去掉,但值得注意的是此时customers_arrival就不再是一个generator,所以不能用env.process(customers_arrival(…)),而是直接运行这个函数:

def customers_arrival(env, num_of_customers, interval_customers, counter):"""定义用户抵达银行的这个随机过程:param env: simpy的仿真环境:param num_of_customers: 用户的总数量:param interval_customers: 用户抵达银行的大致时间间隔:param counter: 银行柜台 (是simpy中的resource):return:"""for i in range(num_of_customers):Customer(env, name=str(i), counter=counter, time_in_bank=12.0)# t = random.expovariate(1.0 / interval_customers)# yield env.timeout(t)env = simpy.Environment()
counter = simpy.Resource(env, capacity=1)  # counter是整个仿真环境的资源,且只有一个柜台
customers_arrival(env, NUM_OF_CUSTOMERS, INTERVAL_CUSTOMERS, counter)
# env.process(customers_arrival(env, NUM_OF_CUSTOMERS, INTERVAL_CUSTOMERS, counter))
env.run()

结果是一样的:

Customer:  0  arrive the bank at:  0
Customer:  1  arrive the bank at:  0
Customer:  2  arrive the bank at:  0
Customer:  3  arrive the bank at:  0
Customer:  4  arrive the bank at:  0
Customer:  0  is ready to use the counter at:  0
Customer:  1  has waited for:  1.1659990389377906  cannot wait any longer!
Customer:  3  has waited for:  1.5290675757676708  cannot wait any longer!
Customer:  2  has waited for:  2.3110494692657597  cannot wait any longer!
Customer:  4  has waited for:  2.856268835569244  cannot wait any longer!
Customer:  0  finished at:  18.837525165283687

这篇关于【SimPy系列博客之官方example学习与解读】—— Example 1: Bank Renege的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/585420

相关文章

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx

Linux系统之authconfig命令的使用解读

《Linux系统之authconfig命令的使用解读》authconfig是一个用于配置Linux系统身份验证和账户管理设置的命令行工具,主要用于RedHat系列的Linux发行版,它提供了一系列选项... 目录linux authconfig命令的使用基本语法常用选项示例总结Linux authconfi

解读docker运行时-itd参数是什么意思

《解读docker运行时-itd参数是什么意思》在Docker中,-itd参数组合用于在后台运行一个交互式容器,同时保持标准输入和分配伪终端,这种方式适合需要在后台运行容器并保持交互能力的场景... 目录docker运行时-itd参数是什么意思1. -i(或 --interactive)2. -t(或 --

解读为什么@Autowired在属性上被警告,在setter方法上不被警告问题

《解读为什么@Autowired在属性上被警告,在setter方法上不被警告问题》在Spring开发中,@Autowired注解常用于实现依赖注入,它可以应用于类的属性、构造器或setter方法上,然... 目录1. 为什么 @Autowired 在属性上被警告?1.1 隐式依赖注入1.2 IDE 的警告:

Rust中的注释使用解读

《Rust中的注释使用解读》本文介绍了Rust中的行注释、块注释和文档注释的使用方法,通过示例展示了如何在实际代码中应用这些注释,以提高代码的可读性和可维护性... 目录Rust 中的注释使用指南1. 行注释示例:行注释2. 块注释示例:块注释3. 文档注释示例:文档注释4. 综合示例总结Rust 中的注释

解读Pandas和Polars的区别及说明

《解读Pandas和Polars的区别及说明》Pandas和Polars是Python中用于数据处理的两个库,Pandas适用于中小规模数据的快速原型开发和复杂数据操作,而Polars则专注于高效数据... 目录Pandas vs Polars 对比表使用场景对比Pandas 的使用场景Polars 的使用

Rust中的Drop特性之解读自动化资源清理的魔法

《Rust中的Drop特性之解读自动化资源清理的魔法》Rust通过Drop特性实现了自动清理机制,确保资源在对象超出作用域时自动释放,避免了手动管理资源时可能出现的内存泄漏或双重释放问题,智能指针如B... 目录自动清理机制:Rust 的析构函数提前释放资源:std::mem::drop android的妙

golang字符串匹配算法解读

《golang字符串匹配算法解读》文章介绍了字符串匹配算法的原理,特别是Knuth-Morris-Pratt(KMP)算法,该算法通过构建模式串的前缀表来减少匹配时的不必要的字符比较,从而提高效率,在... 目录简介KMP实现代码总结简介字符串匹配算法主要用于在一个较长的文本串中查找一个较短的字符串(称为

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

MySQL中的MVCC底层原理解读

《MySQL中的MVCC底层原理解读》本文详细介绍了MySQL中的多版本并发控制(MVCC)机制,包括版本链、ReadView以及在不同事务隔离级别下MVCC的工作原理,通过一个具体的示例演示了在可重... 目录简介ReadView版本链演示过程总结简介MVCC(Multi-Version Concurr