You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

SimPy进程暂停实现及银行服务午休场景建模解决方案

嘿,我来帮你搞定这两个SimPy仿真的问题,咱们一个个来拆解:

问题1:如何在SimPy中实现当另一个进程启动时暂停目标进程

要实现这个需求,核心是利用SimPy的中断机制,配合状态记录来完成进程的暂停与恢复。具体步骤很清晰:

  • 在目标进程中用try-except捕获simpy.Interrupt异常
  • 捕获中断时,记录当前已经执行的时间/进度
  • 当恢复条件满足(比如触发暂停的进程结束),让目标进程继续执行剩余任务

给你一个简单的示例代码,展示进程A被进程B启动后暂停,B结束后A继续的逻辑:

import simpy

def target_process(env):
    total_run_time = 10
    elapsed_time = 0
    try:
        while elapsed_time < total_run_time:
            start = env.now
            # 分小段执行,方便捕获中断
            yield env.timeout(2)
            elapsed_time += env.now - start
            print(f"目标进程已运行 {elapsed_time}/{total_run_time} 时间单位")
    except simpy.Interrupt:
        print(f"目标进程在 {env.now:.2f} 被暂停,已运行 {elapsed_time:.2f} 时间单位")
        # 等待恢复信号(这里模拟等待触发进程结束的时间)
        yield env.timeout(3)
        # 继续执行剩余时间
        remaining_time = total_run_time - elapsed_time
        yield env.timeout(remaining_time)
        print(f"目标进程恢复后,在 {env.now:.2f} 完成全部任务")

def trigger_pause_process(env, target_proc):
    yield env.timeout(5)
    print("触发进程启动,开始暂停目标进程...")
    target_proc.interrupt()
    yield env.timeout(3)
    print("触发进程结束,允许目标进程恢复")

env = simpy.Environment()
target = env.process(target_process(env))
env.process(trigger_pause_process(env, target))
env.run()

问题2:银行柜员午休场景的正确建模方法

你当前的问题是用interrupt()直接中断了客户的服务进程,导致服务无法恢复。其实银行午休更符合现实的逻辑是柜员完成当前客户的服务后再进入午休,如果确实需要强制中断当前服务(比如严格到点停止),也可以通过记录进度来实现后续恢复。下面给你两种可行方案:

方案1:柜员完成当前客户后开始午休(推荐,贴近现实)

这个方案中,午休开始时,柜员不会中断正在处理的客户,而是停止接受新的客户请求,直到午休结束。我们通过控制资源的可用性来实现:

import simpy
import random
import numpy as np

class BANCO(object):
    def __init__(self, env, n_caixas, n_prioritario):
        self.env = env
        self.caixa_prioritario = simpy.PriorityResource(env, n_prioritario)
        self.is_lunch_time = False  # 标记是否处于午休
        self.lunch_end_event = env.event()  # 午休结束事件,用于唤醒等待的客户

    def wait_for_lunch_over(self):
        # 如果正处于午休,等待午休结束事件触发
        if self.is_lunch_time:
            yield self.lunch_end_event

def processo_caixa(env, banco, idade, id):
    print(f'Cliente {id}, {idade:.2f} anos entrou no banco às {env.now:.2f}')
    try:
        # 请求柜员前,先检查是否在午休,是的话就等结束
        yield env.process(banco.wait_for_lunch_over())
        # 优先处理60岁以上的客户(优先级0比1高)
        with banco.caixa_prioritario.request(priority=0 if idade >= 60 else 1) as caixa:
            yield caixa
            print(f'Cliente {id}, {idade:.2f} anos começou o atendimento às {env.now:.2f}')
            # 完成整个服务,不会被午休中断
            yield env.timeout(2)
            print(f'Cliente {id}, {idade:.2f} anos terminou o atendimento às {env.now:.2f}')
            yield env.timeout(0.2)
    except simpy.Interrupt:
        print(f'Cliente {id} foi interrompido durante o atendimento às {env.now:.2f}')

def manutencao(env, banco):
    while True:
        # 模拟工作一段时间后进入午休
        yield env.timeout(3)
        if not banco.is_lunch_time:
            print(f'Pausa para almoço iniciada às {env.now:.2f}')
            banco.is_lunch_time = True
            # 重置午休结束事件(因为事件触发后就无法重复使用)
            banco.lunch_end_event = env.event()
            # 午休持续3个时间单位
            yield env.timeout(3)
            print(f'Fim do almoço às {env.now:.2f}')
            banco.is_lunch_time = False
            # 触发事件,唤醒所有等待午休结束的客户
            banco.lunch_end_event.succeed()
        # 下一次午休前的间隔时间
        yield env.timeout(5)

def rodar(env, n_caixas, n_prioritario):
    banco = BANCO(env, n_caixas, n_prioritario)
    # 启动午休管理进程
    env.process(manutencao(env, banco))
    id=1
    while True:
        idade = np.random.triangular(20,35,90)
        env.process(processo_caixa(env, banco, idade, id))
        yield env.timeout(np.random.standard_exponential())
        id+=1

N_CAIXAS = 1
N_CAIXAS_PRIORITARIOS = 1
TEMPO_SIMULACO = 40

env = simpy.Environment()
env.process(rodar(env, N_CAIXAS, N_CAIXAS_PRIORITARIOS))
env.run(until = TEMPO_SIMULACO)

方案2:强制中断当前服务,午休后恢复

如果你的场景要求必须在午休开始时立即中断当前服务,那么需要记录客户已接受的服务时间,午休结束后重新提交进程完成剩余服务:

import simpy
import random
import numpy as np

class BANCO(object):
    def __init__(self, env, n_caixas, n_prioritario):
        self.env = env
        self.caixa_prioritario = simpy.PriorityResource(env, n_prioritario)
        self.is_lunch_time = False
        self.lunch_end_event = env.event()
        # 存储被中断的客户信息:(剩余服务时间, 客户id, 年龄)
        self.interrupted_customers = []

def processo_caixa(env, banco, idade, id):
    total_service_time = 2  # 总服务时长
    elapsed_service_time = 0
    print(f'Cliente {id}, {idade:.2f} anos entrou no banco às {env.now:.2f}')
    try:
        with banco.caixa_prioritario.request(priority=0 if idade >= 60 else 1) as caixa:
            yield caixa
            print(f'Cliente {id}, {idade:.2f} anos começou o atendimento às {env.now:.2f}')
            # 分小段处理服务,方便检查午休状态
            while elapsed_service_time < total_service_time:
                start = env.now
                # 每次处理0.5个时间单位,然后检查是否进入午休
                yield env.timeout(min(0.5, total_service_time - elapsed_service_time))
                elapsed_service_time += env.now - start
                if banco.is_lunch_time:
                    # 记录剩余服务时间,准备后续恢复
                    banco.interrupted_customers.append((total_service_time - elapsed_service_time, id, idade))
                    print(f'Cliente {id} foi interrompido, tempo restante: {total_service_time - elapsed_service_time:.2f} às {env.now:.2f}')
                    raise simpy.Interrupt
            print(f'Cliente {id}, {idade:.2f} anos terminou o atendimento às {env.now:.2f}')
            yield env.timeout(0.2)
    except simpy.Interrupt:
        pass

def manutencao(env, banco):
    while True:
        yield env.timeout(3)
        if not banco.is_lunch_time:
            print(f'Pausa para almoço iniciada às {env.now:.2f}')
            banco.is_lunch_time = True
            banco.lunch_end_event = env.event()
            yield env.timeout(3)
            print(f'Fim do almoço às {env.now:.2f}')
            banco.is_lunch_time = False
            banco.lunch_end_event.succeed()
            # 恢复被中断的客户,重新提交服务进程
            for remaining_time, id, idade in banco.interrupted_customers:
                print(f'Resumindo atendimento do cliente {id}, tempo restante: {remaining_time:.2f} às {env.now:.2f}')
                env.process(resume_customer(env, banco, idade, id, remaining_time))
            banco.interrupted_customers.clear()
        yield env.timeout(5)

def resume_customer(env, banco, idade, id, remaining_time):
    with banco.caixa_prioritario.request(priority=0 if idade >= 60 else 1) as caixa:
        yield caixa
        print(f'Cliente {id} retomou o atendimento às {env.now:.2f}')
        yield env.timeout(remaining_time)
        print(f'Cliente {id} terminou o atendimento após retomada às {env.now:.2f}')
        yield env.timeout(0.2)

def rodar(env, n_caixas, n_prioritario):
    banco = BANCO(env, n_caixas, n_prioritario)
    env.process(manutencao(env, banco))
    id=1
    while True:
        idade = np.random.triangular(20,35,90)
        env.process(processo_caixa(env, banco, idade, id))
        yield env.timeout(np.random.standard_exponential())
        id+=1

N_CAIXAS = 1
N_CAIXAS_PRIORITARIOS = 1
TEMPO_SIMULACO = 40

env = simpy.Environment()
env.process(rodar(env, N_CAIXAS, N_CAIXAS_PRIORITARIOS))
env.run(until = TEMPO_SIMULACO)

关键改动说明

  1. 方案1通过状态标记+事件等待的方式,让新客户在午休期间自动排队,正在服务的客户不受影响,完全符合银行的实际操作逻辑。
  2. 方案2将服务拆分为小段执行,实时检查午休状态,中断时记录剩余服务时间,午休结束后重新发起服务请求,实现了强制中断后的恢复。

内容的提问来源于stack exchange,提问作者Claudio

火山引擎 最新活动