Recursos com prioridade

Além do recurso como definido nas seções anteriores, o SimPy possui dois tipos específicos de recursos: com prioridade e "peemptivos".

Recursos com prioridade: PriorityResource

Um recurso pode ter uma fila de entidades desejando ocupá-lo para executar determinado processo. Existindo a fila, o recurso será ocupado respeitando a ordem de chegada das entidades (ou a regra FIFO).

Contudo, existem situações em que algumas entidades possuem prioridades sobre as outras, de modo que elas desrespeitam a regra do primeiro a chegar é o primeiro a ser atendido.

Por exemplo, considere um consultório de pronto atendimento de um hospital em que 70% do pacientes são de prioridade baixa (pulseira verde), 20% de prioridade intermediária (pulseira amarela) e 10% de prioridade alta (pulseira vermelha). Existem 2 médicos que realizam o atendimento e que sempre verificam inicialmente a ordem de prioridade dos pacientes na fila. Os pacientes chegam entre si em intervalos exponencialmente distribuídos, com média de 5 minutos e o atendimento é também exponencialmente distribuído, com média de 9 minutos por paciente.

No exemplo, os médicos são recursos, mas também respeitam uma regra específica de prioridade. Um médico ou recurso deste tipo, é criado pelo comando:

medicos = simpy.PriorityResource(env, capacity=capacidade_desejada)

Para a solução do exemplo, o modelo aqui proposto terá 3 funções: uma para sorteio do tipo de pulseira, uma para geração de chegadas de pacientes e outra para atendimento dos pacientes.

Como uma máscara inicial do modelo, teríamos:

import simpy
import random

def sorteiaPulseira():
    # retorna a cor da pulseira e sua prioridade
    pass

def chegadaPacientes(env, medicos):
    # gera pacientes exponencialmente distribuídos
    # sorteia a pulseira
    # inicia processo de atendimento
    pass

def atendimento(env, paciente, pulseira, prio, medicos):
    # ocupa um médico e realiza o atendimento do paciente
    pass

random.seed(100)       
env = simpy.Environment()
medicos = simpy.PriorityResource(env, capacity=2) # cria os 2 médicos
chegadas = env.process(chegadaPacientes(env, medicos))

env.run(until=20)

O preenchimento da máscara pode ser feito de diversas maneiras, um possibilidade seria:

import simpy
import random

def sorteiaPulseira():
    # retorna a cor da pulseira e sua prioridade
    r = random.random()                 # sorteia número aleatório ente 0 e 1
    if r <= .70:                        # 70% é pulseira verde
        return "pulseira verde", 3
    elif r <= .90:                      # 20% (=90-70) é pulseira amarela
        return "pulseira amarela", 2
    return "pulseira vermelha", 1       # 10% (=100-90) é pulseira vermelha

def chegadaPacientes(env, medicos):
    #gera pacientes exponencialmente distribuídos

    i = 0
    while True:
        yield env.timeout(random.expovariate(1/5))
        i += 1

        # sorteia a pulseira
        pulseira, prio = sorteiaPulseira()
        print("%4.1f Paciente %2i com %s chega" %(env.now, i, pulseira))

        # inicia processo de atendimento
        env.process(atendimento(env, "Paciente %2i" %i, pulseira, prio, medicos))

def atendimento(env, paciente, pulseira, prio, medicos):
    # ocupa um médico e realiza o atendimento do paciente

    with medicos.request(priority=prio) as req:
        yield req
        print("%4.1f %s com %s inicia o atendimento" %(env.now, paciente, pulseira))
        yield env.timeout(random.expovariate(1/9))
        print("%4.1f %s com %s termina o atendimento" %(env.now, paciente, pulseira))

random.seed(100)       
env = simpy.Environment()
# cria os médicos
medicos = simpy.PriorityResource(env, capacity=2) 
chegadas = env.process(chegadaPacientes(env, medicos))

env.run(until=20)

O importante a ser destacado é que a prioridade é informada ao request do recurso medicos pelo argumento priority:

with medicos.request(priority=prio) as req:
    yield req

Para o SimPy, quando menor o valor fornecido para o parâmetro priority, maior a prioridade daquela entidade na fila. Assim, a função sorteiaPulseira retorna 3 para a pulseira verde (de menor prioridade) e 1 para a vermelha (de maior prioridade).

Quando o modelo anterior é executado, fornece como saída:

 0.8 Paciente  1 com pulseira verde chega
 0.8 Paciente  1 com pulseira verde inicia o atendimento
 8.2 Paciente  2 com pulseira amarela chega
 8.2 Paciente  2 com pulseira amarela inicia o atendimento
11.0 Paciente  3 com pulseira verde chega
11.4 Paciente  4 com pulseira verde chega
11.7 Paciente  5 com pulseira vermelha chega
11.8 Paciente  1 com pulseira verde termina o atendimento
11.8 Paciente  5 com pulseira vermelha inicia o atendimento
15.5 Paciente  5 com pulseira vermelha termina o atendimento
15.5 Paciente  3 com pulseira verde inicia o atendimento
18.8 Paciente  3 com pulseira verde termina o atendimento
18.8 Paciente  4 com pulseira verde inicia o atendimento

Percebemos que o paciente 5 chegou no instante 11,7 minutos, depois do pacientes 3 e 4, mas iniciou seu atendimento assim que um médico ficou livre no instante 11,8 minutos (exatamente aquele que atendia ao Paciente 1).

Recursos que podem ser interrompidos: PreemptiveResource

Considere, no exemplo anterior, que o paciente de pulseira vermelha tem uma prioridade tal que ele interrompe o atendimento atual do médico e imediatamente é atendido. Os recursos com preemptividade são recursos que aceitam a interrupção da tarefa em execução para iniciar outra de maior prioridade.

Um recurso capaz de ser interrompido é criado pelo comando:

medicos = simpy.PreemptiveResource(env, capacity=capacidade)

Assim, o modelo anterior precisa ser modificado de modo a criar os médicos corretamente:

random.seed(100)       
env = simpy.Environment()
# cria os médicos
medicos = simpy.PreemptiveResource(env, capacity=2)
chegadas = env.process(chegadaPacientes(env, medicos))

env.run(until=20)

Agora, devemos modificar a função atendimentopara garantir que quando um recurso for requisitado por um processo de menor prioridade, ele causará uma interrupção no Python, o que obriga a utilização de bloco de controle de interrupção try:...except.

Quando um recurso deve ser interrompido, o SimPy retorna um interrupção do tipo simpy.Interrupt,como mostrado no código a seguir (noteo bloco try...except dentro da função atendimento):

def atendimento(env, paciente, pulseira, prio, medicos):
    # ocupa um médico e realiza o atendimento do paciente

    with medicos.request(priority=prio) as req:
        yield req
        print("%4.1f %s com %s inicia o atendimento" %(env.now, paciente, pulseira))
        try:
            yield env.timeout(random.expovariate(1/9))
            print("%4.1f %s com %s termina o atendimento" %(env.now, paciente, pulseira))
        except:
            print("%4.1f %s com %s tem atendimento interrompido" %(env.now, paciente, pulseira))

random.seed(100)       
env = simpy.Environment()
# cria os médicos
medicos = simpy.PreemptiveResource(env, capacity=2)
chegadas = env.process(chegadaPacientes(env, medicos))

env.run(until=20)

Quando simulado por apenas 20 minutos, o modelo acrescido das correções apresentadas fornece a seguinte saída:

 0.8 Paciente  1 com pulseira verde chega
 0.8 Paciente  1 com pulseira verde inicia o atendimento
 8.2 Paciente  2 com pulseira amarela chega
 8.2 Paciente  2 com pulseira amarela inicia o atendimento
11.0 Paciente  3 com pulseira verde chega
11.4 Paciente  4 com pulseira verde chega
11.7 Paciente  5 com pulseira vermelha chega
11.7 Paciente  1 com pulseira verde tem atendimento interrompido
11.7 Paciente  5 com pulseira vermelha inicia o atendimento
15.3 Paciente  5 com pulseira vermelha termina o atendimento
15.3 Paciente  3 com pulseira verde inicia o atendimento
18.7 Paciente  3 com pulseira verde termina o atendimento
18.7 Paciente  4 com pulseira verde inicia o atendimento

Note que o Paciente 5 interrompe o atendimento do Paciente 1, como desejado.

Contudo, a implementação anterior está cheia de limitações: pacientes com pulseira amarela não deveriam interromper o atendimento, mas na implementação proposta eles devem interromper o atendimento de pacientes de pulseira verde. Para estas situações, o requestpossui um argumento preempt que permite ligar ou desligar a opção de preemptividade:

with medicos.request(priority=prio, preempt=preempt) as req:
    yield req

O modelo alterado para interromper apenas no caso de pulseiras vermelhas, ficaria (note que o argumento preempt é agora fornecido diretamente a partir da função sorteiaPulseira):

import simpy
import random

def sorteiaPulseira():
    # retorna a cor da pulseira e sua prioridade
    r = random.random() # sorteia número aleatório ente 0 e 1
    if r <= .70: # 70% é pulseira verde
        return "pulseira verde", 3, False
    elif r <= .90: # 20% (=90-70) é pulseira amarela
        return "pulseira amarela", 2, False
    return "pulseira vermelha", 1, True # 10% (=100-90) é pulseira vermelha

def chegadaPacientes(env, medicos):
    #gera pacientes exponencialmente distribuídos

    i = 0
    while True:
        yield env.timeout(random.expovariate(1/5))
        i += 1

        # sorteia a pulseira
        pulseira, prio, preempt = sorteiaPulseira()
        print("%4.1f Paciente %2i com %s chega" %(env.now, i, pulseira))

        # inicia processo de atendimento
        env.process(atendimento(env, "Paciente %2i" %i, pulseira, prio, preempt, medicos))

def atendimento(env, paciente, pulseira, prio, preempt, medicos):
    # ocupa um médico e realiza o atendimento do paciente

    with medicos.request(priority=prio, preempt=preempt) as req:
        yield req
        print("%4.1f %s com %s inicia o atendimento" %(env.now, paciente, pulseira))
        try:
            yield env.timeout(random.expovariate(1/9))
            print("%4.1f %s com %s termina o atendimento" %(env.now, paciente, pulseira))
        except:
            print("%4.1f %s com %s tem atendimento interrompido" %(env.now, paciente, pulseira))

random.seed(100)       
env = simpy.Environment()
# cria os médicos
medicos = simpy.PreemptiveResource(env, capacity=2)
chegadas = env.process(chegadaPacientes(env, medicos))

env.run(until=20)

O modelo anterior, quando executado por apenas 20 minutos, fornece como saída:

 0.8 Paciente  1 com pulseira verde chega
 0.8 Paciente  1 com pulseira verde inicia o atendimento
 8.2 Paciente  2 com pulseira amarela chega
 8.2 Paciente  2 com pulseira amarela inicia o atendimento
11.0 Paciente  3 com pulseira verde chega
11.4 Paciente  4 com pulseira verde chega
11.7 Paciente  5 com pulseira vermelha chega
11.7 Paciente  1 com pulseira verde tem atendimento interrompido
11.7 Paciente  5 com pulseira vermelha inicia o atendimento
15.3 Paciente  5 com pulseira vermelha termina o atendimento
15.3 Paciente  3 com pulseira verde inicia o atendimento
18.7 Paciente  3 com pulseira verde termina o atendimento
18.7 Paciente  4 com pulseira verde inicia o atendimento

Conteúdos desta seção

Desafios

Desafio 11: acrescente ao último programa proposto o cálculo do tempo de atendimento que ainda falta de atendimento para o paciente que foi interrompido por outro e imprima o resultado na tela.

Desafio 12: quando um paciente é interrompido, ele deseja retornar ao atendimento de onde parou. Altere o programa para que um paciente de pulseira verde interrompido possa retornar para ser atendido no tempo restante do seu atendimento. Dica: altere a numeração de prioridades de modo que um paciente verde interrompido tenha prioridade superior ao de um paciente verde que acabou de chegar.

Last updated