M/M/c Process based
In yet another view we look at entities (e.g. messages, customers, jobs, goods) undergoing a process as they flow through a DES. A process can be viewed as a sequence of events separated by time intervals. Often entities or processes share limited resources. Thus they have to wait for them to become available and then to undergo a transformation (e.g. transport, treatment or service) taking some time.
This view can be expressed as processes waiting and delaying on a clock or implicitly blocking until they can take!
something from a Channel
or put!
it back. To implement the M/M/c we start right with describing the server
and arrivals
processes:
using DiscreteEvents, Printf, Distributions, Random
function server(clk::Clock, id::Int, input::Channel, output::Channel, X::Distribution)
job = take!(input)
print(clk, @sprintf("%5.3f: server %d serving customer %d\n", tau(clk), id, job))
delay!(clk, X)
print(clk, @sprintf("%5.3f: server %d finished serving %d\n", tau(clk), id, job))
put!(output, job)
end
function arrivals(clk::Clock, queue::Channel, N::Int, X::Distribution)
for i = 1:N # initialize customers
delay!(clk, X)
put!(queue, i)
print(clk, @sprintf("%5.3f: customer %d arrived\n", tau(clk), i))
end
end
Note that
take!
,put!
ordelay!
block the caller until resources are available or till a given simulation time.- Also asynchronous tasks must
print
via the clock to avoid clock concurrency. - Therefore functions representing processes need a
Clock
variable as their first argument.
Next we setup our constants, the simulation environment with clock, channels and processes and run:
Random.seed!(8710) # set random number seed for reproducibility
const N = 10 # total number of customers
const c = 2 # number of servers
const μ = 1.0 / c # service rate
const λ = 0.9 # arrival rate
const M₁ = Exponential(1/λ) # interarrival time distribution
const M₂ = Exponential(1/μ) # service time distribution
# initialize the simulation environment and run
clock = Clock()
input = Channel{Int}(Inf)
output = Channel{Int}(Inf)
for i in 1:c
process!(clock, Prc(i, server, i, input, output, M₂))
end
process!(clock, Prc(0, arrivals, input, N, M₁), 1)
run!(clock, 20)
We get the following output:
0.123: customer 1 arrived
0.123: server 1 serving customer 1
0.226: customer 2 arrived
0.226: server 2 serving customer 2
....
9.475: customer 9 arrived
9.475: server 2 serving customer 9
10.027: server 1 finished serving 8
10.257: customer 10 arrived
10.257: server 1 serving customer 10
10.624: server 1 finished serving 10
10.734: server 2 finished serving 9
"run! finished with 50 clock events, 0 sample steps, simulation time: 20.0"
Note that:
- the times deviate from the activity based implementation because here we do not use conditional events and therefore have no time divergence due to sampling [1],
- the printing via the clock produces additional clock events,
- here we don't see sampling since no conditional events are involved.
- 1the
load
activity in the activity-based example uses a conditional event. The condition is then checked periodically with sampling. That introduces a time divergence into the simulation. Instead in the process-based example the blocking on channels is handled by Julia internally and we need not to wait conditionally on the clock.