All resources share the same basic concept: The resource itself is some kind of
a container with a, usually limited, capacity. Processes can either try to
put something into the resource or try to get something out. If the
resource is full or empty, they have to queue up and wait.

This is roughly, how every resource looks like:

BaseResource(capacity):
put_queue
get_queue
put(): event
get(): event

Every resources a maximum capacity and two queues, one for processes that want
to put something into it and one for processes that want to get something out.
The put() and get() methods both return an event that is triggered when
the corresponding action was successful.

While a process is waiting for a put or get event to succeed, it may be
interrupted by another process. After
catching the interrupt, the process has two possibilities:

It may continue to wait for the request (by yielding the event again).

It may stop waiting for the request. In this case, it has to call the
event’s cancel() method.

Since you can easily forget this, all resources events are context
managers (see the Python docs for
details).

The resource system is modular and extensible. Resources can, for example, use
specialized queues and event types. This allows them to use sorted queues, to
add priorities to events, or to offer preemption.

Resources can be used by a limited number of processes at a time (e.g.,
a gas station with a limited number of fuel pumps). Processes request these
resources to become a user (or to “own” them) and have to release them once
they are done (e.g., vehicles arrive at the gas station, use a fuel-pump, if
one is available, and leave when they are done).

Requesting a resources is modeled as “putting a process’ token into the
resources” and releasing a resources correspondingly as “getting a process’
token out of the resource”. Thus, calling request()/release() is
equivalent to calling put()/get(). Releasing a resource will always
succeed immediately.

The Resource is conceptually a semaphore. Its only parameter – apart from
the obligatory reference to an Environment – is its
capacity. It must be a positive number and defaults to 1: Resource(env,capacity=1).

Instead of just counting its current users, it stores the request event as an
“access token” for each user. This is, for example, useful for adding
preemption (see below).

Note, that you have to release the resource under all conditions; for example,
if you got interrupted while waiting for or using the resource. In order to
help you with that and to avoid too many try:...finally:... constructs,
request events can be used as context manager:

As you may know from the real world, not every one is equally important. To map
that to SimPy, there’s the PriorityResource. This subclass of Resource lets
requesting processes provide a priority for each request. More important
requests will gain access to the resource earlier than less important ones.
Priority is expressed by integer numbers; smaller numbers mean a higher
priority.

Sometimes, new requests are so important that queue-jumping is not enough and
they need to kick existing users out of the resource (this is called
preemption). The PreemptiveResource allows you to do exactly this:

PreemptiveResource inherits from PriorityResource and adds a preempt
flag (that defaults to True) to request(). By setting this to False
(resource.request(priority=x,preempt=False)), a process can decide to not
preempt another resource user. It will still be put in the queue according to
its priority, though.

The implementation of PreemptiveResource values priorities higher than
preemption. That means preempt request are not allowed to cheat and jump over
a higher prioritized request. The following example shows that preemptive low
priority requests cannot queue-jump over high priority requests:

Process A requests the resource with priority 0. It immediately becomes
a user.

Process B requests the resource with priority -2 but sets preempt to
False. It will queue up and wait.

Process C requests the resource with priority -1 but leaves preemptTrue. Normally, it would preempt A but in this case, B is queued up
before C and prevents C from preempting A. C can also not preempt
B since its priority is not high enough.

Thus, the behavior in the example is the same as if no preemption was used at
all. Be careful when using mixed preemption!

Due to the higher priority of process B, no preemption occurs in this
example. Note that an additional request with a priority of -3 would be able
to preempt A.

If your use-case requires a different behaviour, for example queue-jumping or
valuing preemption over priorities, you can subclass PreemptiveResource and
override the default behaviour.

Containers allow you to retrieve their current level as well as their
capacity (see GasStation.monitor_tank() and tanker()). You can also
access the list of waiting events via the put_queue and get_queue
attributes (similar to Resource.queue).

Using Stores you can model the production and consumption of concrete objects
(in contrast to the rather abstract “amount” stored in containers). A single
Store can even contain multiple types of objects.

Beside Store, there is a FilterStore that lets you use
a custom function to filter the objects you get out of the store.

Here is a simple example modelling a generic producer/consumer scenario:

As with the other resource types, you can get a store’s capacity via the
capacity attribute. The attribute items points to the list of items
currently available in the store. The put and get queues can be accessed via
the put_queue and get_queue attributes.

FilterStore can, for example, be used to model machine shops where machines
have varying attributes. This can be useful if the homogeneous slots of
a Resource are not what you need: