Priority: Processes, Resources, Events, and Queues
The word “priority” means four different, independent things in
cimba.sim, one per mechanism. They share a sign convention – a higher
signed 64-bit integer always means higher priority, and ties always fall
back to arrival order (FIFO) – but setting one does not set the others.
This page collects all four in one place so the distinction (and the
similarity) is easy to check.
Mechanism |
Set with |
Compared against |
Tie-break |
|---|---|---|---|
Process priority |
|
other processes waiting on the same resource/pool |
arrival order |
Event priority |
|
other events due at the same simulated time |
scheduling order |
Priority-queue priority |
|
other objects in the same priority queue |
put order |
(no separate resource/pool priority) |
– |
uses the process’s priority, see below |
– |
All four default to 0 and accept any int, including negative
values, so “push to the very back” is just as expressible as “push to the
front.”
Process priority: resource and pool queueing
A process’s own priority governs how it queues for a sim.Resource or
sim.Pool. Set it once at registration or change it at runtime:
class Clinic(sim.Model):
@sim.process(priority=5)
def vip_patient(self: "Clinic"):
...
@sim.process
def regular_patient(self: "Clinic"):
me = sim.current()
sim.set_priority(me, -2)
...
When several processes are waiting on the same .acquire(), the
highest-priority waiter goes first; among waiters with equal priority,
whoever asked first is served first. Changing a process’s priority while it
is already waiting moves it to its new place in that order.
.preempt() uses the same numbers to decide who can be displaced, not
just who waits longest:
sim.Resource.preempt()takes the resource from the current holder only if the caller’s priority is strictly higher than the holder’s; otherwise it falls back to waiting politely in the normal queue.sim.Pool.preempt(amount)starts by taking capacity from the lowest-priority holder, working forward, and stops as soon as it would have to take from a holder with equal or higher priority than the caller – any shortfall waits normally.
Cimba avoids priority inversion in the basic case (a run of lower-priority
waiters cannot starve a higher-priority one indefinitely), but it does not
reorder the queue to let a request further back jump ahead just because it
could be satisfied sooner (e.g. three units free, the front waiter wants
five, and three waiters behind it each want one). If that kind of
skip-ahead matters for your model, adjust priorities explicitly with
sim.set_priority() to bring the right process to the front.
See Components for the acquire/release/preempt walkthrough and
Tutorial: Modeling with Cimba Python (Acquiring, preempting, holding, and releasing resources, with interruptions) for a stress-tested example with both
polite (acquire()) and aggressive (preempt()) agents competing for
one pool.
Event priority: same-time ordering
env.<event>.schedule()/.schedule_at() take a priority= keyword,
and the handle they return has its own .reprioritize(priority):
class Clinic(sim.Model):
log_snapshot: sim.Event
close_shift: sim.Event
@sim.event(field="log_snapshot")
def record_snapshot(self: "Clinic"):
...
@sim.event(field="close_shift")
def handle_close(self: "Clinic"):
...
@sim.process
def driver(self: "Clinic"):
self.log_snapshot.schedule(0.0, priority=-100) # run last today
self.close_shift.schedule(480.0, priority=10) # run first at t=480
Event priority only matters when two or more events are due at exactly the same simulated time: the higher-priority one fires first, and among equal priorities, whichever was scheduled first fires first. A common use is making sure a “close the day” event runs before other things scheduled for the same instant, or pushing a diagnostic/logging event to run last (a large negative priority) so it observes everyone else’s final state.
See Events, Timers, and Signals for the full scheduling API.
Priority-queue priority: explicit ordering, independent of the caller
A sim.PQueues element’s priority is a value you choose per object, with
no link to the priority of whichever process happened to put it there:
GOLD_CARD_PRIORITY = 5
class Attraction(sim.Component):
@sim.process
def visitor(self, env, vip: Visitor):
priority = GOLD_CARD_PRIORITY if vip.gold_card else 0
entry = env.ride_queue[0].put(sim.current(), priority)
Objects come out highest-priority first; among equal priorities, whoever
was put in earliest comes out first. .reprioritize(entry, priority)
moves an existing entry (used for jockeying: reconsidering a wait and
asking for a better spot), and .position(entry) reports an entry’s
current 1-based rank so a process can decide whether it is worth waiting
or switching lines.
See Tutorial: Modeling with Cimba Python (Agents balking, reneging, and jockeying in queues) for the full balking/jockeying/reneging park example, which uses priority queues exactly this way for a “gold card” fast lane.
Keeping the four straight
Because all four mechanisms share the same “bigger number wins, ties go to whoever arrived first” rule, it is tempting to assume they are the same knob. They are not: a process’s priority does not automatically become the priority of an event it schedules or an object it puts in a priority queue – if you want that coupling (e.g. a VIP process’s own priority should also jump its requests to the front of a ride queue), pass it through explicitly, as in the gold-card example above.