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