SimPy Store Explained: Managing Distinct Objects
Resources count slots. Containers track levels. Stores hold distinct items.
When you need to pass actual objects between processes—parts, messages, jobs—Store is your tool.
When to Use Store
Resource: "Give me any available server."
Container: "Give me 10 litres of fuel."
Store: "Give me the next item in the queue."
import simpy
env = simpy.Environment()
store = simpy.Store(env, capacity=10)
Basic Operations
# Put an item
yield store.put("item1")
# Get an item (FIFO)
item = yield store.get()
print(item) # "item1"
Items are objects. Strings, numbers, custom classes—anything.
Producer-Consumer Pattern
def producer(env, store):
for i in range(5):
item = f"Product_{i}"
yield store.put(item)
print(f"Produced {item} at {env.now}")
yield env.timeout(2)
def consumer(env, store):
while True:
item = yield store.get()
print(f"Consumed {item} at {env.now}")
yield env.timeout(3)
env = simpy.Environment()
store = simpy.Store(env)
env.process(producer(env, store))
env.process(consumer(env, store))
env.run(until=20)
Blocking Behaviour
put() blocks when store is full:
store = simpy.Store(env, capacity=3)
yield store.put("a") # OK
yield store.put("b") # OK
yield store.put("c") # OK
yield store.put("d") # Blocks until space available
get() blocks when store is empty:
store = simpy.Store(env)
item = yield store.get() # Blocks until something is put
Custom Objects
Stores shine with complex objects:
class Part:
def __init__(self, part_id, part_type, priority):
self.part_id = part_id
self.part_type = part_type
self.priority = priority
def producer(env, store):
for i in range(10):
part = Part(i, "widget", priority=i % 3)
yield store.put(part)
yield env.timeout(1)
def consumer(env, store):
while True:
part = yield store.get()
print(f"Processing part {part.part_id} ({part.part_type})")
yield env.timeout(2)
Checking Store State
store.items # List of items currently in store
store.capacity # Maximum capacity (inf if not specified)
len(store.items) # Current count
Unlimited Capacity
By default, stores have infinite capacity:
store = simpy.Store(env) # No limit
For bounded buffers, specify capacity:
store = simpy.Store(env, capacity=50)
FilterStore: Selective Getting
What if you need a specific type of item? Use FilterStore:
store = simpy.FilterStore(env, capacity=10)
# Put various items
yield store.put({'type': 'red', 'id': 1})
yield store.put({'type': 'blue', 'id': 2})
yield store.put({'type': 'red', 'id': 3})
# Get only red items
red_item = yield store.get(lambda item: item['type'] == 'red')
print(red_item) # {'type': 'red', 'id': 1}
The filter function decides which items match.
PriorityStore: Priority-Based Getting
Get items by priority:
from simpy import PriorityItem
store = simpy.PriorityStore(env, capacity=10)
yield store.put(PriorityItem(priority=3, item="low"))
yield store.put(PriorityItem(priority=1, item="high"))
yield store.put(PriorityItem(priority=2, item="medium"))
item = yield store.get() # Gets "high" (priority 1)
Lower priority number = higher priority (retrieved first).
Real-World Example: Job Queue
class Job:
def __init__(self, job_id, job_type, duration):
self.job_id = job_id
self.job_type = job_type
self.duration = duration
self.created = None
def job_creator(env, job_queue):
job_id = 0
while True:
job = Job(job_id, "standard", random.uniform(5, 15))
job.created = env.now
yield job_queue.put(job)
print(f"Job {job_id} created at {env.now}")
job_id += 1
yield env.timeout(random.expovariate(1/3))
def worker(env, name, job_queue):
while True:
job = yield job_queue.get()
wait_time = env.now - job.created
print(f"{name} starts job {job.job_id} (waited {wait_time:.1f})")
yield env.timeout(job.duration)
print(f"{name} finished job {job.job_id}")
env = simpy.Environment()
job_queue = simpy.Store(env)
env.process(job_creator(env, job_queue))
env.process(worker(env, "Worker1", job_queue))
env.process(worker(env, "Worker2", job_queue))
env.run(until=50)
Store vs Resource
| Feature | Resource | Store |
|---|---|---|
| What it holds | Slots | Objects |
| Put operation | request() |
put(item) |
| Get operation | N/A (release) | get() |
| Items returned | No | Yes |
| Custom items | No | Yes |
Use Resource when you just need capacity. Use Store when you need the actual items.
Store vs Container
| Feature | Container | Store |
|---|---|---|
| What it tracks | Levels/amounts | Distinct items |
| Put amount | put(quantity) |
put(item) |
| Get amount | get(quantity) |
get() (one item) |
| Items are | Fungible | Distinct |
Use Container for fuel, inventory levels. Use Store for parts, messages, jobs.
Common Patterns
Message Passing
mailbox = simpy.Store(env)
def sender(env, mailbox):
yield mailbox.put({'type': 'greeting', 'content': 'Hello'})
def receiver(env, mailbox):
message = yield mailbox.get()
print(f"Received: {message['content']}")
Conveyor/Buffer
buffer = simpy.Store(env, capacity=20)
def machine_a(env, buffer):
while True:
part = produce_part()
yield buffer.put(part)
yield env.timeout(production_time)
def machine_b(env, buffer):
while True:
part = yield buffer.get()
process(part)
yield env.timeout(processing_time)
Summary
Store: - Holds distinct items, not slots or levels - FIFO by default - Use FilterStore for selective gets - Use PriorityStore for priority-based gets - Perfect for jobs, messages, parts, orders
Pass objects between processes. Use Store.
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