中断 Simpy 中较早的超时事件

问题描述

我想创建一个带有服务器的系统,这些服务器需要时间进行设置才能提供服务。每当有客户到达队列时,就会建立一个服务器,较早到来的客户将占用较早开启的服务器,如下所示。

  1. 客户 1 到达并请求服务器。
  2. 服务器 1 在 t1 秒内完成设置。
  3. 客户 2 到达并请求服务器。
  4. 服务器 2 在 t2 秒内设置完毕。
  5. 服务器 2 已开启。
  6. 客户 1 占用服务器 2。

由于这里的出色回答,此过程已成功模拟: Simulating a system of resource with set-up/switch-on times using Simpy

我想向系统添加一个策略。当客户离开系统时,他会检查是否还有其他客户在等待服务。如果是这样,他保持服务器保持开启状态,否则,他立即关闭服务器。

  1. 客户 1 完成服务并离开系统。
  2. 服务器 2 保持开启状态。

客户 2 看到服务器 2 在服务器 1 之前开启,并且看到没有其他人在等待,因此他关闭了服务器 1。

  1. 客户 2 占用服务器 2。
  2. 服务器 1(仍处于 SETUP 模式)已关闭

因此,7),8),9),10) 同时发生,并且事件 7) 的发生触发了更早的事件 2) 在 10 中的中断).

是否可以在 Server_Management() 类中管理此类中断?

"""
Simulation of a dynamic server pool

Server pool starts empty and servers are added as needed,but there is a delay 
simulating start up time before the server is available to fulfill a resource request

Programmer: Matt
    Wrote original version

Programmer: Michael R. Gibbs
    Added server check for dynamicaly adding servers
    Fixed return of resorces to pool
"""

import simpy

LAM = 8  #arival rate of jobs
MU = 2  #service rate
ALPHA = 12  #set up rate
NUM_SERVERS = 3
MAX_NUM_JOB = 10000000000
UNTIL = 5

num_current_jobs = 0  #global variable which record the number of jobs present in the system

def generate_interarrival():
  return np.random.exponential(1/LAM)

def generate_service():
  return np.random.exponential(1/MU)

def switch_on():
  return np.random.exponential(1/ALPHA)

class Generate_Job(): 
    def arriving_job(env,servers):
        global  num_current_jobs,num_server_on,leaving_time_list
        for i in range(MAX_NUM_JOB):
            job = Job(name="Job%01d" % (i))
            yield env.timeout(generate_interarrival())
            print('{0:.5f}'.format(env.Now),job.name,"arrives")
            num_current_jobs +=1
            env.process(job.handling(env,servers))

class Server_Management():
    def check_servers_arriving(env,servers):
        global  num_current_jobs
        """
        Checks the server pool to see if the pool has any avalable servers
        if not then add a server,(there will be a delay before added server becomes available)

        Call this without a yield so it does not block if a server is added
        """

        print('{0:.5f}'.format(env.Now),"checking #OFF servers:",max(0,NUM_SERVERS-num_current_jobs+1))
        
        if num_current_jobs <= NUM_SERVERS:
            # will need another server
            switch_on_time = switch_on()
            print('{0:.5f}'.format(env.Now),"adding a server at " + '{0:.5f}'.format(env.Now + switch_on_time) + " --")
            yield env.timeout(switch_on_time)    #switch on time
            yield servers.put(1)     
            print('{0:.5f}'.format(env.Now),"added a server--")

    def check_servers_leaving(env,servers):
        global  num_current_jobs
                """
        Checks the queue to see if there is any customer is waiting
        """
        print('{0:.5f}'.format(env.Now),"checking queue length:",num_current_jobs-NUM_SERVERS))

        if num_current_jobs >= NUM_SERVERS:  #if there is any waiting customer
            yield servers.put(1)             #Keep the computer remain ON
            print('{0:.5f}'.format(env.Now),"computer remains ON--")


class Room:                             # A room containing servers (resource)
    def __init__(self,env):
        self.computer = simpy.Container(env,capacity = NUM_SERVERS,init = 0)

class Job(object):
    def __init__(self,name):
        self.name = name
    
    def handling(self,env,servers):
        global num_current_jobs,job_list
        # added a check to see if a resource pool needs another server.
        env.process(Server_Management.check_servers_arriving(env,servers.computer))
        print('{0:.5f}'.format(env.Now),self.name,"requesting a server--") 
        with servers.computer.get(1) as req:
            yield req 
            print('{0:.5f}'.format(env.Now),"occupies a server--")        
            yield env.timeout(generate_service())    #service time
            print('{0:.5f}'.format(env.Now),"leaves")
            num_current_jobs -= 1
        # added a check to see if a resource pool needs another server.
        env.process(Server_Management.check_servers_leaving(env,servers.computer))

np.random.seed(0)
env = simpy.Environment()
servers = Room(env)
env.process(Generate_Job.arriving_job(env,servers))
env.run(until = UNTIL) 

解决方法

所以我没有中断服务器的添加,而是在启动延迟后添加了一个检查,看看是否仍然需要资源。这简化了尝试确定哪个服务器启动以取消并避免必须设置异常处理

我还添加了 return_server 进程,如果请求队列不为空,则只将服务器添加回资源池

下一个增强功能是允许一两台服务器保持待机状态,以帮助缩短平均响应时间。

告诉我这是否适合您

"""
Simulation of a dynamic server pool

Server pool starts empty and servers are added as needed,but there is a delay 
simulating start up time before the server is available to fulfill a resource request
After a server is started a check is made to see if the server is still needed before
it is added to the resouce pool

Programmer: Matt
    Wrote original version

Programmer: Michael R. Gibbs
    Added server check for dynamicaly adding servers
    servers are returned to resouce pool only if needed (get queue size > 0)
"""

import simpy
import numpy as np

LAM = 8  #arival rate of jobs
MU = 2  #service rate
ALPHA = 12  #set up rate
NUM_SERVERS = 5
MAX_NUM_JOB = 10000000000
UNTIL = 10

def generate_interarrival():
  return np.random.exponential(1/LAM)

def generate_service():
  return np.random.exponential(1/MU)

def switch_on():
  return np.random.exponential(1/ALPHA)

def return_server(env,servers):
    """
    checks if the server is still needed,if so add back to the resource pool so waiting request can be filled
    else,do not add back to resource pool simulating shutdown
    """

    if len(servers.get_queue) > 0:
        # server is still needed
        yield servers.put(1)
        print('{0:.5f}'.format(env.now),"queuing server --")
    else:
        print('{0:.5f}'.format(env.now),"shutting down server --")

def check_servers(env,servers):
    """
    Checks the server pool to see if the pool has any avalable servers
    if not then add a server,(there will be a delay before added server becomes available)

    after the start up delay,check again to see if the server is still needed

    Call this without a yield so it does not block if a server is added
    """

    print('{0:.5f}'.format(env.now),"checking server pool","requests:",len(servers.get_queue),"servers:",servers.level)
    
    if len(servers.get_queue) >= servers.level:
        # will need another server
        d = switch_on()
        startT = env.now + d

        print('{0:.5f}'.format(env.now),"adding a server at " + '{0:.5f}'.format(startT) + " --")
        
        # start up
        yield env.timeout(d)    #switch on time

        # check if server is still needed
        if len(servers.get_queue) > 0:
            # still need it so add
            yield servers.put(1)      
            print('{0:.5f}'.format(env.now),"added a server--")
        else:
            print('{0:.5f}'.format(env.now),"server not needed,not added--")
            
class Generate_Job(): 
    def arriving_job(env,servers):
        global  num_current_jobs,num_server_on,leaving_time_list
        for i in range(MAX_NUM_JOB):
            job = Job(name="Job%01d" % (i))
            yield env.timeout(generate_interarrival())
            print('{0:.5f}'.format(env.now),job.name,"arrives")

            env.process(job.handling(env,servers))

class Room:                             # A room containing servers (resource)
    def __init__(self,env):
        self.computer = simpy.Container(env,capacity = 10000,init = 0)


class Job(object):
    def __init__(self,name):
        self.name = name

    def handling(self,env,servers):

        # added a check to see if a resource pool needs another server.
        env.process(check_servers(env,servers.computer))
        print('{0:.5f}'.format(env.now),self.name,"requesting a server--") 

        with servers.computer.get(1) as req: 
            
            yield req 
            # if the queue is empty then the req is never filled and the next lines are never called
            # need to do this before the rescource requests
            #                       
            # yield env.timeout(switch_on())    #switch on time
            # yield servers.server.put(1)            
            print('{0:.5f}'.format(env.now),"occupies a server--")        
            yield env.timeout(generate_service())    #service time
            print('{0:.5f}'.format(env.now),"leaves")

            # containers do not return a resouce at the end of a "with"
            # added a put
            #yield servers.computer.put(1)
            yield env.process(return_server(env,servers.computer))


np.random.seed(0)
env = simpy.Environment()
servers  = Room(env)
env.process(Generate_Job.arriving_job(env,servers))
env.run(until = UNTIL) 
,

好奇并编写代码,我需要休息一会儿并完成一些付费工作

"""
Simulation of a dynamic server pool

Server pool starts empty and servers are added as needed,but there is a delay 
simulating start up time before the server is available to fulfill a resource request
After a server is started a check is made to see if the server is still needed before
it is added to the resouce pool

Programmer: Matt
    Wrote original version

Programmer: Michael R. Gibbs
    Added server check for dynamicaly adding servers
    servers are returned to resouce pool only if needed (get queue size > 0)
"""

import simpy
import numpy as np

LAM = 8  #arival rate of jobs
MU = 2  #service rate
ALPHA = 12  #set up rate
NUM_SERVERS = 5
MAX_NUM_JOB = 50 #10000000000
UNTIL = 10

server_cnt = 0
job_cnt = 0
start_up_list = []

def generate_interarrival():
  return np.random.exponential(1/LAM)

def generate_service():
  return np.random.exponential(1/MU)

def switch_on():
  return np.random.exponential(1/ALPHA)

def return_server(env,do not add back to resource pool simulating shutdown
    """

    global server_cnt,start_up_list,job_cnt

    if len(servers.get_queue) > 0:
        # server is still needed
        yield servers.put(1)
        print('{0:.5f}'.format(env.now),"queuing server --")

        if server_cnt > job_cnt: 
            # have a extra server,try to kill starting up server

            # first clean up events that have already happend
            i = len(start_up_list)-1
            while i >= 0:
                e = start_up_list[i]
                if e.triggered:
                    start_up_list.pop(i)
                i -=1

            # kill last added startup process hoping that is the one with longest time before start up finishes
            if len(start_up_list) > 0:
                e = start_up_list.pop()
                e.interrupt()
                print('{0:.5f}'.format(env.now),"killing start up server --------------------------------")
    else:
        print('{0:.5f}'.format(env.now),"shutting down server --")
        server_cnt -= 1

def check_servers(env,check again to see if the server is still needed

    Call this without a yield so it does not block if a server is added
    """

    global server_cnt

    print('{0:.5f}'.format(env.now),"idel:",servers.level,server_cnt)
    
    if len(servers.get_queue) >= servers.level and server_cnt < NUM_SERVERS:
        # will need another server
        server_cnt += 1
        d = switch_on()
        startT = env.now + d

        print('{0:.5f}'.format(env.now),"adding a server at " + '{0:.5f}'.format(startT) + " --")
        
        try: # catch interrupts exceptions
            # start up
            yield env.timeout(d)    #switch on time

            # check if server is still needed
            if len(servers.get_queue) > 0:
                # still need it so add
                yield servers.put(1)      
                print('{0:.5f}'.format(env.now),"added a server--")
            else:
                print('{0:.5f}'.format(env.now),not added--")
                server_cnt -=1
        except:
            server_cnt -= 1
            print('{0:.5f}'.format(env.now),"server starting at " + '{0:.5f}'.format(startT) + " has been killed --")

class Generate_Job(): 
    def arriving_job(env,servers):

        global start_up_list,job_cnt

        # added a check to see if a resource pool needs another server.
        job_cnt += 1

        start_evt = env.process(check_servers(env,servers.computer))
        start_up_list.append(start_evt)
        print('{0:.5f}'.format(env.now),"leaves")

            # containers do not return a resouce at the end of a "with"
            # added a put
            #yield servers.computer.put(1)
            job_cnt -= 1
            yield env.process(return_server(env,servers))
env.run(until = UNTIL)