How to Model a Queue in SimPy: The Complete Guide
Queues are the bread and butter of discrete event simulation. Customers waiting. Jobs pending. Packets buffering. If you can model a queue, you can model almost anything.
The Basic Queue
A queue needs: 1. Arrivals - Things entering the system 2. Server - Resource that serves them 3. Service - Time to process each arrival
import simpy
import random
def customer(env, name, server):
arrival = env.now
print(f"{name} arrives at {arrival:.2f}")
with server.request() as req:
yield req
wait = env.now - arrival
print(f"{name} waited {wait:.2f}, starts service at {env.now:.2f}")
service_time = random.expovariate(1/5) # Mean 5
yield env.timeout(service_time)
print(f"{name} leaves at {env.now:.2f}")
def arrivals(env, server):
i = 0
while True:
yield env.timeout(random.expovariate(1/6)) # Mean 6
env.process(customer(env, f"Customer {i}", server))
i += 1
env = simpy.Environment()
server = simpy.Resource(env, capacity=1)
env.process(arrivals(env, server))
env.run(until=100)
That's an M/M/1 queue—Poisson arrivals, exponential service, one server.
Collecting Statistics
A simulation without statistics is just a story. Track what matters:
class QueueStats:
def __init__(self):
self.wait_times = []
self.service_times = []
self.queue_lengths = []
def record_wait(self, wait):
self.wait_times.append(wait)
def record_service(self, service):
self.service_times.append(service)
def summary(self):
return {
'avg_wait': sum(self.wait_times) / len(self.wait_times),
'max_wait': max(self.wait_times),
'avg_service': sum(self.service_times) / len(self.service_times),
'customers_served': len(self.wait_times)
}
stats = QueueStats()
def customer(env, name, server, stats):
arrival = env.now
with server.request() as req:
yield req
stats.record_wait(env.now - arrival)
service_time = random.expovariate(1/5)
yield env.timeout(service_time)
stats.record_service(service_time)
# After simulation
print(stats.summary())
Multiple Servers
Add capacity:
# 3 parallel servers
servers = simpy.Resource(env, capacity=3)
Now up to 3 customers can be served simultaneously.
Priority Queues
Some customers matter more:
server = simpy.PriorityResource(env, capacity=1)
def vip_customer(env, server):
with server.request(priority=1) as req: # Lower = higher priority
yield req
yield env.timeout(5)
def regular_customer(env, server):
with server.request(priority=10) as req:
yield req
yield env.timeout(5)
VIPs jump the queue.
Balking (Refusing to Join)
Customers who leave when the queue is too long:
def customer(env, server, max_queue):
if len(server.queue) >= max_queue:
print(f"Customer balks at {env.now}")
return
with server.request() as req:
yield req
yield env.timeout(random.expovariate(1/5))
Reneging (Giving Up)
Customers who leave after waiting too long:
def customer(env, server, patience):
arrival = env.now
with server.request() as req:
result = yield req | env.timeout(patience)
if req in result:
yield env.timeout(random.expovariate(1/5))
else:
print(f"Customer reneged after {env.now - arrival:.2f}")
Queue Length Over Time
Monitor the queue:
queue_log = []
def monitor(env, server, interval=1):
while True:
queue_log.append({
'time': env.now,
'queue_length': len(server.queue),
'in_service': server.count
})
yield env.timeout(interval)
env.process(monitor(env, server))
Visualise:
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame(queue_log)
plt.step(df['time'], df['queue_length'], where='post')
plt.xlabel('Time')
plt.ylabel('Queue Length')
plt.title('Queue Length Over Time')
plt.show()
Multi-Stage Queues
Customer visits multiple stations:
def customer(env, check_in, security, boarding):
# Check-in
with check_in.request() as req:
yield req
yield env.timeout(random.uniform(2, 5))
# Security
with security.request() as req:
yield req
yield env.timeout(random.uniform(3, 10))
# Boarding
with boarding.request() as req:
yield req
yield env.timeout(random.uniform(1, 3))
env = simpy.Environment()
check_in = simpy.Resource(env, capacity=4)
security = simpy.Resource(env, capacity=2)
boarding = simpy.Resource(env, capacity=1)
Classic Queue Notation
| Notation | Meaning |
|---|---|
| M/M/1 | Poisson arrivals, exponential service, 1 server |
| M/M/c | Same, but c servers |
| M/G/1 | Poisson arrivals, general service, 1 server |
| G/G/c | General arrivals and service, c servers |
SimPy can model any of these.
Utilisation
How busy is the server?
def calculate_utilisation(stats, simulation_time, capacity):
total_service = sum(stats.service_times)
return total_service / (simulation_time * capacity)
utilisation = calculate_utilisation(stats, 100, 1)
print(f"Utilisation: {utilisation:.1%}")
Or track it over time:
busy_time = 0
last_check = 0
def track_utilisation(env, server):
global busy_time, last_check
while True:
yield env.timeout(1)
busy_time += server.count # 1 if busy, 0 if idle
last_check = env.now
Complete Example
import simpy
import random
import pandas as pd
import matplotlib.pyplot as plt
class MMcQueue:
def __init__(self, env, num_servers, arrival_rate, service_rate):
self.env = env
self.server = simpy.Resource(env, capacity=num_servers)
self.arrival_rate = arrival_rate
self.service_rate = service_rate
self.wait_times = []
self.queue_log = []
def customer(self, name):
arrival = self.env.now
with self.server.request() as req:
yield req
self.wait_times.append(self.env.now - arrival)
yield self.env.timeout(random.expovariate(self.service_rate))
def arrivals(self):
i = 0
while True:
yield self.env.timeout(random.expovariate(self.arrival_rate))
self.env.process(self.customer(f"C{i}"))
i += 1
def monitor(self, interval=1):
while True:
self.queue_log.append({
'time': self.env.now,
'queue': len(self.server.queue),
'busy': self.server.count
})
yield self.env.timeout(interval)
def run(self, until):
self.env.process(self.arrivals())
self.env.process(self.monitor())
self.env.run(until=until)
def report(self):
print(f"Customers served: {len(self.wait_times)}")
print(f"Average wait: {sum(self.wait_times)/len(self.wait_times):.2f}")
print(f"Max wait: {max(self.wait_times):.2f}")
print(f"90th percentile: {sorted(self.wait_times)[int(0.9*len(self.wait_times))]:.2f}")
# Run simulation
random.seed(42)
env = simpy.Environment()
queue = MMcQueue(env, num_servers=2, arrival_rate=1/3, service_rate=1/5)
queue.run(until=1000)
queue.report()
Summary
Queuing in SimPy: - Use Resource for servers - Use request() context manager - Track wait times, queue lengths, utilisation - Add balking/reneging for realism - Chain resources for multi-stage systems
Master the queue. Master simulation.
Next Steps
Discover the Power of Simulation
Want to become a go-to expert in simulation with Python? The Complete Simulation Bootcamp will show you how simulation can transform your career and your projects.
Explore the Bootcamp