- Chapter 25 Cordinated Resources for Event-Driven Simulations
Cordinated Resources for Event-Driven Simulations
The three kinds of simulation objects, consumable, nonconsumable, and renewable, coordinate access to quantifiable static resources. Coordination is also needed in order to synchronize the tasks of two or more simulation objects. For example, a car washer only carries out its tasks when a vehicle appears in the car wash; a bank teller gives service to a customer when the customer appears in the bank.
The mechanism for providing synchronization of tasks among two or more SimulationObjects is supported by class ResourceCoordinator. Class ResourceCoordinator is a concrete subclass of class Resource; class Resource was defined in Chapter 24. The purpose of this chapter is to describe the implementation of ResourceCoordinator and to give several examples using this synchronization technique.
The Implementation of Class ResourceCoordinator
A ResourceCoordinator represents a SimulationObject whose tasks must be synchronized with the tasks of another SimulationObject. One of the objects is considered the resource or the "customer"; the other object acquires this resource in order to give it service and can, therefore, be thought of as a "server" or clerk. At any given time, customers may be waiting for a server or servers may be waiting for customers, or no one may be waiting. Only one queue has to be maintained; a variable of a ResourceCoordinator keeps track of whether that queue contains customers, servers, or is empty. The variable pending, inherited from the superclass Resource, refers to the queue; the variable whoIsWaiting refers to the status of the queue.
Three inquiries can be made of a ResourceCoordinator, are there customers waiting? (customersWaiting), are there servers waiting? (serversWaiting), and how many are waiting? (queueLength). The message acquire comes from a SimulationObject acting as a server who wants to acquire a customer to serve. If a customer is waiting, then the SimulationObject can give it service (giveService); otherwise, the SimulationObject is added to the queue which is set to be a queue of servers waiting. The message producedBy: aCustomer comes from a SimulationObject acting as a customer who wants to be served. If a server is waiting, then the SimulationObject can get service (getServiceFor: aCustomerRequest); otherwise, the SimulationObject is added to the queue which is set to be a queue of customers waiting.
In all cases, the queue consists of instances of DelayedEvent. If the queue consists of customers, then the DelayedEvent condition is the SimulationObject waiting to get service. If the queue consists of servers, then the DelayedEvent condition is nil until a customer is acquired, at which point the condition is set to be the customer request (itself a DelayedEvent that was stored when the customer request was first made). When the Delayed Event is resumed, the condition is returned to the requestor; in this way, a server gains access to a customer request. Once the synchronized tasks are completed, the server can resume the customer's activities by resuming the request.
|instance variable names||whoIsWaiting|
accessing customersWaiting ↑whoIsWaiting = = #customer serversWaiting ↑whoIsWaiting = = #server queueLength ↑pending size task language acquire | anEvent | self customersWaiting ifTrue: [↑self giveService]. anEvent ← DelayedEvent new. whoIsWaiting ← #server. self addRequest: anEvent. "At this point, the condition of anEvent has been set to the customer request." ↑anEvent condition producedBy: aCustomer | anEvent | anEvent ← DelayedEvent onCondition: aCustomer. self serversWaiting ifTrue: [↑self getServiceFor: anEvent]. whoIsWaiting ← #customer. self addRequest: anEvent private getServiceFor: aCustomerRequest | aServerRequest | aServerRequest ← pending removeFirst. pending isEmpty ifTrue: [whoIsWaiting ← #none]. aServerRequest condition: aCustomerRequest. aServerRequest resume. ActiveSimulation stopProcess. aCustomerRequest pause. ActiveSimulation startProcess giveService | aCustomerRequest | aCustomerRequest ← pending removeFirst. pending isEmpty ifTrue: [whoIsWaiting ← #none]. ↑aCustomerRequest setName: aString super setName: aString. whoIsWaiting ← #none
Notice that when a server gives service to customer, the customer request is suspended (pause) in which case the simulation process reference count must be decremented until the service task is resumed.
Example: A Car Wash Simulation
The example of a CarWash simulation consists of cars that arrive and ask to be washed or washed and waxed. Washers are available to do the washing and waxing; when there are no cars to service, the Washers are idle. The definition of the simulation CarWash follows. There is one resource coordinator for the various car customers. Cars arrive for washing about one every 20 minutes; cars arrive for washing and waxing about one every 30 minutes. The Washer arrives when the CarWash first starts and stays as long as the simulation proceeds.
initialization defineArrivalSchedule self scheduleArrivalOf: Wash accordingTo: (Exponential mean: 20). self scheduleArrivalOf: WashAndWax accordingTo: (Exponential mean: 30). self scheduleArrivalOf: Washer new at: 0.0 defineResources self coordinate: 'CarCustomer'
Each kind of car customer can report the service it requires. The Washer tasks depend on the kind of service each car customer wants. First a customer is obtained. Then it is given service (wash, wax, or both) and then the customer's own activities are continued.
simulation control tasks | carRequest | [true] whileTrue: [carRequest ← self acquireResource: 'CarCustomer'. (carRequest condition wants: 'wash') ifTrue: [self holdFor: (Uniform from: 12.0 to: 26.0) next]. (carRequest condition wants: 'wax') ifTrue: [self holdFor: (Uniform from: 8.0 to: 12.0) next]. self resume: carRequest]
The vehicles Wash and WashAndWax are defined next. Each contains an attribute which defines the kinds of service they require. The tasks of these vehicles are simply to ask for service and, after getting the service, to leave.
|instance variable names||service|
accessing wants: aService ↑service includes: aService simulation control initialize super initialize. service ← #('wash') tasks self produceResource: 'CarCustomer'
simulation control initialize super initialize. service ← #('wash' 'wax')
WashAndWax is defined as a subclass of Wash since the only difference between the two is setting the service attributes. The following trace was produced by making Wash a subclass of EventMonitor.
|0||Wash I enters|
|0||Wash I wants to get service as CarCustomer|
|0||WashAndWax 1 enters|
|0||WashAndWax 1 wants to get service as CarCustomer|
|0||Washer 1 enters|
|0||Washer 1 wants to serve for CarCustomer|
|0||Washer 1 can serve Wash 1|
|0||Washer 1 holds for 14.2509|
|7.95236||WashAndWax 2 enters|
|7.95236||WashAndWax 2 wants to get service as CarCustomer|
|8.42388||Wash 2 enters|
|8.42388||Wash 2 wants to get service as CarCustomer|
|12.9404||Wash 3 enters|
|12.9404||Wash 3 wants to get service as CarCustomer|
|14.2509||Washer 1 resumes Wash 1|
|14.2509||Washer 1 wants to serve for CarCustomer|
|14.2509||Washer 1 can serve WashAndWax 1|
|14.2509||Washer 1 holds for 25.502 (wash part)|
|14.2509||Wash 1 exits|
|26.6023||WashAndWax 3 enters|
|26.6023||WashAndWax 3 wants to get service as CarCustomer|
|26.8851||Wash 4 enters|
|26.8851||Wash 4 wants to get service as CarCustomer|
|29.5632||Wash 5 enters|
|29.5632||Wash 5 wants to get service as CarCustomer|
|32.1979||Wash 6 enters|
|32.1979||Wash 6 wants to get service as CarCustomer|
|38.7616||Wash 7 enters|
|38.7616||Wash 7 wants to get service as CarCustomer|
|39.753||Washer 1 holds for 9.21527 (wax part)|
|43.5843||Wash 8 enters|
|43.5843||Wash 8 wants to get service as CarCustomer|
|48.9683||Washer 1 resumes WashAndWax 1|
|48.9683||Washer 1 wants to serve for CarCustomer|
|48.9683||Washer 1 can serve WashAndWax 2|
|48.9683||Washer 1 holds for 14.2757 (wash part)|
|48.9683||WashAndWax 1 exits|
|51.8478||WashAndWax 4 enters|
|51.8478||WashAndWax 4 wants to get service as CarCustomer|
|63.244||Washer 1 holds for 11.7717 (wax part)|
|68.9328||Wash 9 enters|
|68.9328||Wash 9 wants to get service as CarCustomer|
|70.6705||WashAndWax 5 enters|
|70.6705||WashAndWax 5 wants to get service as CarCustomer|
|75.0157||Washer 1 resumes WashAndWax 2|
|75.0157||Washer 1 wants to serve for CarCustomer|
|75.0157||Washer 1 can serve Wash 2|
|75.0157||Washer 1 holds for 18.6168|
|75.0157||WashAndWax 2 exits|
|78.0228||Wash 10 enters|
|78.0228||Wash 10 wants to get service as CarCustomer|
|78.2874||WashAndWax 6 enters|
|78.2874||WashAndWax 6 wants to get service as CarCustomer|
At 78.2874, there are 12 customers waiting--8 are Wash and 4 are WashAndWax; 2 Wash and 2 WashAndWax have been served.
From this trace one can see that more Washers are needed to service the demand in this CarWash. It is also possible to collect specific data on the amount of time customers wait (using the duration statistics gathering technique and throughput histogram from the previous chapter) and the percentage of time a worker is busy or idle.
Example: A Ferry Service for a Special Truck
The last example in Chapter 24 was of a ferry service in which a ferry crosses between an island and the mainland carrying cars. The cars were modeled as static resources that a ferry could acquire. The ferry's task was to load as many as six cars, cross over the water, unload the cars it carried, and repeat the process. The example was like one provided in the book on Demos by Graham Birtwistle. In that book, Birtwistle describes the ferry service as coordinating the ferry service with the travels of a Truck. A Truck goes from the mainland to the island in order to make deliveries, returns to the mainland to get more supplies, and then goes to the island again, etc. The ferry only crosses from one side to the other if it is carrying the Truck. This version of the ferry Simulation requires a coordination of SimulationObjects representing the ferry and the Truck; each has its own tasks to do, but the Truck can not do its tasks without the assistance of the ferry and the ferry has no tasks to do in the absence of a Truck to carry.
Ferry service starts at 7:00 a.m. (420.0 minutes into the day) and ends at 7:00 p.m. (1140 minutes into the day).
initialization defineArrivalSchedule self scheduleArrivalOf: Truck new at: 420.0. self scheduleArrivalOf: Ferry new at: 420.0. defineResources self coordinate: 'TruckCrossing'
The Truck and the Ferry are defined in terms of producing and acquiring the resource 'TruckCrossing'.
simulation control tasks | TruckRequest | [ActiveSimulation time > 1140.0] whileFalse: [TruckRequest ← self acquireResource: 'TruckCrossing'. self load. self crossOver. self unload. self resume: TruckRequest] load self holdFor: 5.0 unload self holdFor: 3.0 crossOver self holdFor: (Normal mean: 8 deviation: 0.5) next
The Truck delivers supplies on the island and picks up supplies on the mainland.
simulation control tasks [true] whileTrue: [self produceResource: 'TruckCrossing'. self deliverSupplies. self produceResource: 'TruckCrossing'. self pickUpSupplies] deliverSupplies self holdFor: (Uniform from: 15 to: 30) next pickUpSupplies self holdFor: (Uniform from: 30 to: 45) next
There is no check in the definition of Truck or Ferry for a particular side, mainland or island, because we assume that both simulations objects start on the same side (the mainland) and their synchronization for crossing over guarantees that they stay on the same side. A trace of the events for running FerrySimulation is
|Start at the mainland|
|420.0||Ferry wants to serve for TruckCrossing|
|420.0||Truck wants to get service as TruckCrossing|
|420.0||Ferry can serve Truck|
|420.0||Ferry load Truck|
|420.0||Ferry holds for 5.0|
|425.0||Ferry cross over|
|425.0||Ferry holds for 7.84272|
|unload the Truck at the island side|
|432.843||Ferry unload Truck|
|432.843||Ferry holds for 3.0|
|435.843||Ferry resumes Truck|
|435.843||Ferry wants to serve for TruckCrossing|
|435.843||Truck deliver supplies|
|435.843||Truck holds for 21.1949|
|457.038||Truck wants to get service as TruckCrossing|
|457.038||Ferry can serve Truck|
|457.038||Ferry toad Truck|
|457.038||Ferry holds for 5.0|
|cross over back to the mainland|
|462.038||Ferry cross over|
|462.038||Ferry holds for 8.28948|
|470.327||Ferry unload Truck|
|470.327||Ferry holds for 3.0|
|473.327||Ferry resumes Truck|
|473.327||Ferry wants to serve for TruckCrossing|
|473.327||Truck pick up supplies|
|473.327||Truck holds for 40.0344|
|513.361||Truck wants to get service as TruckCrossing|
|513.361||Ferry can serve Truck|
|513.361||Ferry load Truck|
|513.361||Ferry holds for 5.0|
|back to the island|
|518.361||Ferry cross over|
|518.361||Ferry holds for 8.05166|
|526.413||Ferry unload Truck|
|526.413||Ferry' holds for 3.0|
|529.413||Ferry resumes Truck|
|529.413||Ferry wants to serve for TruckCrossing|
|529.413||Truck delivers supplies|
|529.413||Truck holds for 27.1916|
|556.605||Truck wants to get service as TruckCrossing|
|556.605||Ferry can serve Truck|
|556.605||Ferry load Truck|
|556.605||Ferry holds for 5.0|
|back to mainland, etc.|
|561.605||Ferry cross over|
|561.605||Ferry holds for 7.53188|
|569.137||Ferry unload Truck|
|569.137||Ferry holds for 3.0|
|572.136||Ferry resumes Truck|
|572.136||Ferry wants to serve for TruckCrossing|
|572.136||Truck pick up supplies|
|572.136||Truck holds for 36.8832|
The ferry tasks do not guarantee that the resting place for the evening is the mainland side. This can be done by monitoring which side the ferry is on and then stopping only after returning to the mainland. Only the definition of Ferry must change since the Truck side is synchronized with it.
|instance variable names||currentSide|
simulation control initialize super initialize. currentSide ← 'Mainland' tasks | TruckRequest finished | finished ← false [finished] whileFalse: [TruckRequest ← self acquireResource: 'TruckCrossing'. self load. self crossOver. self unload. self resume: TruckRequest. finished ← ActiveSimulation time > 1140.0 and: [currentSide = 'Mainland']] load self holdFor: 5.0 unload self holdFor: 3.0 crossOver self holdFor: (Normal mean: 8 deviation: 0.5) next. currentSide ← currentSide = 'Mainland' ifTrue: ['Island'] ifFalse: ['Mainland']
Suppose now that cars can arrive at the ferry ramp in order to be carried across. The ferry can carry as many as 4 cars in addition to the Truck, but the ferry will not cross over unless there is a Truck to carry. Then the definition of the simulation changes by the addition of cars arriving at the mainland or the island; we introduce cars in the same way we did in Chapter 24--as static resources.
initialization defineArrivalSchedule self scheduleArrivalOf: Truck new at: 420.0. self scheduleArrivalOf: Ferry new at: 420.0. self scheduleArrivalOf: MainlandArrival accordingTo: (Exponential parameter: 0.15) startingAt: 420.0. self scheduleArrivalOf: IslandArrival accordingTo: (Exponential parameter: 0.15) startingAt: 420.0 defineResources self coordinate: 'TruckCrossing'. self produce: 3 ofResource: 'Mainland'. self produce: 0 ofResource: 'Island'
The definitions for MainlandArrival and Island Arrival are the same as those given in Chapter 24.
simulation control tasks self produce: 1 ofResource: 'Mainland'
simulation control tasks self produce: 1 ofResource: 'Island'
The Ferry now must take into consideration loading and unloading any cars waiting at the ramp of the current side.
|instance variable names||currentSide|
simulation control initialize super initialize. currentSide ← 'Mainland'. carsOnBoard ← 0. tasks | TruckRequest finished | finished ← false. [finished] whileFalse: [TruckRequest ← self acquireResource: 'TruckCrossing'. self load. self crossOver. self unload. self resume: TruckRequest. finished ← ActiveSimulation time > 1140.0 and: [currentSide = 'Mainland']] load "load the Truck first" self holdFor: 5.0 "now load any cars that are waiting on the current side" [carsOnBoard < 4 and: [self inquireFor: 1 ofResource: currentSide]] whileTrue: [self acquire: 1 ofResource: currentSide. self holdFor: 0.5. carsOnBoard ← carsOnBoard + 1] unload "unload the cars and the Truck" self holdFor: (carsOnBoard * 0.5) + 3.0 crossOver self holdFor: (Normal mean: 8 deviation: 0.5) next. currentSide ← currentSide = 'Mainland' ifTrue: ['Island'] ifFalse: ['Mainland']
Example: A Bank
A bank teller only carries out tasks when a customer appears at the teller's window and asks for service. Since the teller's services are dependent on the needs of the customer, the specification of the teller's tasks includes requests for information from the customer.
Suppose the bank assigns two bank tellers to work all day. The bank opens at 9:00 a.m. and closes at 3:00 p.m. Throughout the day, customers arrive every 10 minutes with a standard deviation of 5 minutes. At the noon lunch hour, the number of customers increases dramatically, averaging one every three minutes. Two additional bank tellers are added to handle the increased demand for service. When a regular bank teller is not serving a customer, the teller has desk work to do.
The arrival of customers and regular workers into the bank is scheduled in the response to the message defineArrivalSchedule to BankSimulation. The lunchtime increase is represented by a discrete probability distribution that is a sample space of twenty 3's, representing a total of the 60 minutes during which lunchtime customers appear. Mixed with the normal load of customers, this means that 20 or more customers appear in that busiest hour.
We define simulation objects BankTeller, LunchtimeTeller, BankCustomer, as well as the BankSimulation.
|class variable names||Hour|
initialization defineArrivalSchedule self scheduleArrivalOf: BankCustomer accordingTo: (Normal mean: 10 deviation: 5) startingAt: 9*Hour. self scheduleArrivalOf: (BankTeller name: 'first') at: 9*Hour self scheduleArrivalOf: (BankTeller name: 'second') at: 9*Hour. self scheduleArrivalOf: BankCustomer accordingTo: (SampleSpaceWithoutReplacement data: ((1 to: 20) collect: [ :i | 3])) startingAt: 12*Hour. self schedule: [self hireMoreTellers] at: 12*Hour. self schedule: [self finishUp] at: 15*Hour defineResources self coordinate: 'TellerCustomer' simulation control hireMoreTellers self schedule: [(LunchtimeTeller name: 'first') startUp] after: 0.0. self schedule: [(LunchtimeTeller name: 'second') startUp] after: 0.0
The ResourceCoordinator is responsible for matching customers (takers of service) with bank tellers (givers of service). The bank customer's task is simple. After entering the bank, the customer gets the attention of a bank teller and asks for service. After obtaining service, the customer leaves. The amount of time the customer spends in the bank depends on how long the customer must wait for a teller and how long the teller takes giving service.
simulation control tasks self produceResource: 'TellerCustomer'
The bank teller's tasks depend on the needs of the customer. To keep this example simple, we will assume that a BankTeller does about the same work for each customer, taking between 2 and 15 minutes. Another alternative would be to give each BankCustomer a list of desired services as was done in the car wash example; the BankTeller would enumerate over the set, taking times appropriate to each kind of service. Whenever a customer is not available, the teller does other tasks. These tasks are small and take a short amount of time; however, the teller can not be interrupted. When one of these background desk tasks is completed, the teller checks to see if a customer has arrived and is waiting for service.
simulation control tasks | customerRequest | [true] whileTrue: [(self numberOfProvidersOfResource: 'TellerCustomer') > 0 ifTrue: [self counterWork] ifFalse: [self deskWork]] counterWork | customerRequest | customerRequest ← self acquireResource: 'TellerCustomer'. self holdFor: (Uniform from: 2 to: 15) next. self resume: customerRequest deskWork self holdFor: (Uniform from: 1 to: 5) next
A LunchtimeTeller has to schedule itself to leave the bank after an hour.
This scheduling can be specified in the response to initialize. When it is time to leave, the LunchtimeTeller has to make certain that all its tasks are completed before leaving. Therefore, a signal (getDone) is set to true the first time the message finishUp is sent; this signal is checked each time the teller's counter work completes. The second time finishUp is sent, the signal is true and the teller can exit. The LunchtimeTeller, unlike the regular BankTeller, does not do any desk work when customers are not available.
|instance variable names||getDone|
initialization initialize super initialize. getDone ← false. ActiveSimulation schedule: [self finishUp] after: 60 simulation control finishUp getDone ifTrue: [super finishUp] ifFalse: [getDone ← true] tasks [getDone] whileFalse: [self counterWork]. self finishUp
A partial trace of the events follows.
|540||BankCustomer 1 enters|
|540||BankCustomer 1 wants to get service as TellerCustomer|
|540||BankTeller first enters|
|540||BankTeller first wants to serve for TellerCustomer|
|540||BankTeller first can serve BankCustomer 1|
|540||BankTeller first holds for 9.33214|
|540||BankTeller second enters|
|540||BankTeller second does desk work|
|540||BankTeller second holds for 3.33594|
|543.336||BankTeller second does desk work|
|543.336||BankTeller second holds for 2.96246|
|546.298||BankTeller second does desk work|
|546.298||BankTeller second holds for 3.56238|
|549.332||BankTeller first resumes BankCustomer 1|
|549.332||BankTeller first does desk work|
|549.332||BankTeller first holds for 1.83978|
|549.332||BankCustomer 1 exits|
|549.819||BankCustomer 2 enters|
|549.819||BankCustomer 2 wants to get service as TellerCustomer|
|549.861||BankTeller second wants to serve for TellerCustomer|
|549.861||BankTeller second can serve BankCustomer 2|
|549.861||BankTeller second holds for 14.3192|
|551.172||BankTeller first does desk work|
|551.172||BankTeller first holds for 4.16901|
|555.341||BankTeller first does desk work|
|555.341||BankTeller first holds for 2.60681|
|557.948||BankTeller first does desk work|
|557.948||BankTeller first holds for 4.58929|
|559.063||BankCustomer 3 enters|
|559.063||BankCustomer 3 wants to get service as TellerCustomer|
|562.537||BankTeller first wants to serve for TellerCustomer|
|562.537||BankTeller first can serve BankCustomer 3|
|562.537||BankTeller first holds for 13.4452|
|564.18||BankTeller second resumes BankCustomer 2|
|564.18||BankTeller second does desk work|
|564.18||BankTeller second holds for 2.63007|
|564.18||BankCustomer 2 exits|
|565.721||BankCustomer 4 enters|
|565.721||BankCustomer 4 wants to get service as TellerCustomer|
|566.81||BankTeller second wants to serve for TellerCustomer|
|566.81||BankTeller second can serve BankCustomer 4|
|566.81||BankTeller second holds for 5.08139|
|571.891||BankTeller second resumes BankCustomer 4|
|571.891||BankTeller second does desk work|
|571.891||BankTeller second holds for 4.69818|
|571.891||BankCustomer 4 exits|
|575.982||BankTeller first resumes BankCustomer 3|
|575.982||BankTeller first does desk work|
|575.982||BankTeller first holds for 2.10718|
|575.982||BankCustomer 3 exits|
|576.59||BankTeller second does desk work|
|576.59||BankTeller second holds for 4.04327|
... and so on until lunch hour when the extra help arrives; at this point, 18 customers have entered; BankTeller first is giving BankCustomer 18 service...
|720||BankCustomer 19 enters|
|720||BankCustomer 19 wants to get service as TellerCustomer|
|720||LunchtimeTeller first enters|
|720||LunchtimeTeller first wants to serve for TellerCustomer|
|720||LunchtimeTeller first can serve BankCustomer 19|
|720||LunchtimeTeller first holds for 11.9505|
|720||LunchtimeTeller second enters|
|720||LunchtimeTeller second wants to serve for TellerCustomer|
|721.109||BankTeller second does desk work|
|721.109||BankTeller second holds for 2.09082|
|721.663||BankTeller first resumes BankCustomer 18|
|721.663||BankTeller first does desk work|
|721.663||BankTeller first holds for 3.43219|
|721.663||BankCustomer 18 exits|
|722.085||BankCustomer 20 enters|
|722.085||BankCustomer 20 wants to get service as TellerCustomer|
|722.085||LunchtimeTeller second can serve BankCustomer 20|
|722.085||LunchtimeTeller second holds for 9.51483|
|723||BankCustomer 21 enters|
|723||BankCustomer 21 wants to get service as TellerCustomer|
|723.2||BankTeller second wants to serve for TellerCustomer|
|723.2||BankTeller second can serve BankCustomer 21|
|723.2||BankTeller second holds for 9.66043|
|725.095||BankTeller first does desk work|
|725.095||BankTeller first holds for 4.97528|
|726||BankCustomer 22 enters|
|726||BankCustomer 22 wants to get service as TellerCustomer|
|729||BankCustomer 23 enters|
|729||BankCustomer 23 wants to get service as TellerCustomer|
|730.071||BankTeller first wants to serve for TellerCustomer|
|730.071||BankTeller first can serve BankCustomer 22|
|730.071||BankTeller first holds for 8.17746|
|731.6||LunchtimeTeller second resumes BankCustomer 20|
|731.6||LunchtimeTeller second wants to serve for TellerCustomer|
|731.6||LunchtimeTeller second can serve BankCustomer 23|
|731.6||LunchtimeTeller second holds for 6.27971|
|731.6||BankCustomer 20 exits|
|731.95||LunchtimeTeller first resumes BankCustomer 19|
|731.95||LunchtimeTeller first wants to serve for TellerCustomer|
|731.95||BankCustomer 19 exits|
|732||BankCustomer 24 enters|
|732||BankCustomer 24 wants to get service as TellerCustomer|
|732||LunchtimeTeller first can serve BankCustomer 24|
|732||LunchtimeTeller first holds for 9.52138|
... BankCustomer 40 just left and lunch time is over; there are 3 other customers in the bank; as soon as they finish with their current customers, the LunchtimeTellers will leave...
|780.0||BankCustomer 44 enters|
|780.0||BankCustomer 44 wants to get service as TellerCustomer|
|780.918||BankTeller first wants to serve for TellerCustomer|
|780.918||BankTeller first can serve BankCustomer 44|
|780.918||BankTeller first holds for 13.1566|
|781.968||BankCustomer 45 enters|
|781.968||BankCustomer 45 wants to get service as TellerCustomer|
|784.001||LunchtimeTeller second resumes BankCustomer 43|
|784.001||LunchtimeTeller second exits|
|784.001||BankCustomer 43 exits|
|787.879||LunchtimeTeller first resumes BankCustomer 42|
|787.879||LunchtimeTeller first exits|
|787.879||BankCustomer 42 exits|
|789.189||BankTeller second resumes BankCustomer 41|
|789.189||BankTeller second wants to serve for TellerCustomer|
|789.189||BankTeller second can serve BankCustomer 45|
|789.189||BankTeller second holds for 2.38364|
|789.189||BankCustomer 41 exits|
|791.572||BankTeller second resumes BankCustomer 45|
|791.572||BankTeller second does desk work|
|791.572||BankTeller second holds for 2.34467|
|791.572||BankCustomer 45 exits|
|793.917||BankTeller second does desk work|
|793.917||BankTeller second holds for 3.19897|
and so on...
The data that would be collected here includes the busy/idle percentages of the tellers and the customers' average wait time.
Example: An Information System
Our last example is also in the Birtwistle book and , according to Birtwistle, is a popular example for simulation systems such as GPSS. The example is an information system simulation that describes remote terminals at which users can arrive and make retrieval requests. A customer with a query arrives at one or the other of the terminals and queues, if necessary, to use it. The system scanner rotates from terminal to terminal seeing if a request is waiting and, if so, provides service. Service means that the scanner copies the query to a buffer unit capable of holding three queries simultaneously; if no buffer position is available, the copying process must wait until one becomes available. Once copying to the buffer succeeds, the system processes the query and places the answer in the buffer to return to the terminal without need for the scanner again.
Using the data provided by Birtwistle, we will model a system with six terminals. Customers arrive at terminals with an exponential mean time of 5 minutes. The buffers are modeled as static resources; the terminals are also static resources; while the terminal services are objects whose tasks are coordinated with the tasks of the queries.
initialization defineArrivalSchedule "Schedule many queries and only one scanner" self scheduleArrivalOf: Query accordingTo: (Exponential parameter: 5) startingAt: 0.0. self scheduleArrivalOf: SystemScanner new at: 0.0 defineResources self produce: 3 ofResource: 'Buffer'. 0 to: 5 do: [ :n | self produce: 1 ofResource: 'Terminal', n printString. self coordinate: 'TerminalService', n printString]
In the above method, we use string concatenation to form the attribute names of six terminals as static resources and six terminal services as coordinated services; the names are Terminal0,...,Terminal5 and TerminalService0,...,TerminalService5.
The ResourceCoordinators for terminal service for each of the six terminals are being handled differently here than in the bank and car wash simulation examples. At anytime, the queues of customers or servers will contain only one or no elements. During the simulation, a Query will enter a queue to wait for terminal service. A SystemScanner moves from coordinator to coordinator, round-robin fashion, to act as the giver of service if a Query is waiting.
The customer, a Query, must first access a terminal resource to get a reply. On accessing one of the six terminals, the customer keys in a request and awaits the reply. It takes between 0.3 and 0.5 minutes (uniformly distributed) to enter a query. Then terminal service is requested. The query now waits for the SystemScanner; when the SystemScanner notices the waiting query, it gives it the needed service. This means that the SystemScanner obtains a buffer slot for the query and copies the request into the buffer. It takes 0.0117 minutes to transfer a query to the buffer. Now the reply can be transferred to the terminal and read, the buffer can be freed up, and the terminal released. It takes between 0.05 and 0.10 (uniformly distributed) to process a query, and 0.0397 minutes to transfer the reply back to the terminal. Customers take between 0.6 and 0.8 minutes (uniformly distributed) to read a reply.
scheduling tasks | terminal terminalNum | "pick a terminal" terminalNum ← (SampleSpace data: #('0' '1' '2' '3' '4' '5')) next. "get a terminal resource" terminal ← self acquire: 1 ofResource: 'Terminal', terminalNum. "got the terminal, now enter the query" self holdFor: (Uniform from: 0.3 to: 0.5) next. "act as a resource for a terminal service in order to process the query" self produceResource: 'TerminalService', terminalNum. "the query is now processed; now read the reply" self holdFor: (Uniform from: 0.6 to: 0.8) next. "and release the terminal" self release: terminal
The scanner's job is to rotate from terminal to terminal seeing if a request is pending and, if so, to wait to transfer a query into a buffer and then move on. Scanner rotation takes 0.0027 minutes and the same amount of time to test a terminal.
|instance variable names||n|
simulation control initialize super initialize. n ← 5 tasks | terminalServiceRequest buffer test | [true] whileTrue: [n ← (n + 1) \\ 6. self holdFor: 0.0027. test ← self numberOfProvidersOfResource: 'TerminalService', n printString. self holdFor: 0.0027. test = 0 ifFalse: [terminalServiceRequest ← self acquireResource: ('TerminalService', n printString). buffer ← self acquire: 1 ofResource: 'Buffer'. "copy the request" self holdFor: 0.0117. "process the query" self holdFor: (Uniform from: 0.05 to: 0.10) next. "return the reply to the terminal" self holdFor: 0.0397. "done, release the resources" self release: buffer. self resume: terminalServiceRequest]]
The SystemScanner is not idle when no Query is waiting; it continually moves from terminal to terminal checking for a Query. This movement stops when a Query is found in order to provide service. When the service is completed, the SystemScanner returns to circulating around, looking for another Query.
Not much happens at first. The first Query enters but it holds for 0.360428 units of time in order to enter its query at Terminal1; meanwhile the SystemScanner moves around the terminals. A second Query enters at 0.0472472 and requests Terminal3; a third enters at 0.130608 and requests Terminal4. At 0.360428, the first Query requests service at Terminal which is given a short time later at time 0.367198. Between this time and time 0.478235, the SystemScanner gives service by getting a Buffer, copying the query to the Buffer, processing it, transferring the reply, and then releasing the Buffer and resuming the Query. In the meantime, the second Query requested service, and a fourth Query entered at Terminal4. The SystemScanner then rotates to Terminal3 to give service to the second Query waiting there.
|0.0||Query 1 enters|
|0.0||Query 1 requests 1 of Terminal1|
|0.0||Query 1 obtained 1 of Terminal1|
|0.0||Query 1 holds for 0.360428|
|0.0||SystemScanner holds for 0.0027|
|0.0027||SystemScanner holds for 0.0027|
|0.0054||SystemScanner holds for 0.0027|
|0.0432||SystemScanner holds for 0.0027|
|0.0459||SystemScanner holds for 0.0027|
|0.0472472||Query 2 enters|
|0.0472472||Query 2 requests 1 of Terminal3|
|0.0472472||Query 2 obtained 1 of Terminal3|
|0.0472472||Query 2 holds for 0.363611|
|0.0486||SystemScanner holds for 0.0027|
|0.0513||SystemScanner holds for 0.0027|
|0.1269||SystemScanner holds for 0.0027|
|0.1296||SystemScanner holds for 0.0027|
|0.130608||Query 3 enters|
|0.130608||Query 3 requests 1 of Terminal4|
|0.130608||Query 3 obtained 1 of Terminal4|
|0.130608||Query 3 holds for 0.445785|
|0.1323||SystemScanner holds for 0.0027|
|0.135||SystemScanner holds for 0.0027|
|0.356398||SystemScanner holds for 0.0027|
|0.359098||SystemScanner holds for 0.0027|
|0.360428||Query 1 wants to get service as TerminalService1|
|0.361798||SystemScanner holds for 0.0027|
|0.364498||SystemScanner holds for 0.0027|
|0.367198||SystemScanner wants to give service as TerminalService1|
|0.367198||SystemScanner can serve as TerminalService1|
|0.367198||SystemScanner requests 1 of Buffer|
|0.367198||SystemScanner obtained 1 of Buffer|
|0.367198||SystemScanner holds for 0.0117|
|0.378898||SystemScanner holds for 0.0596374|
|0.410858||Query 2 wants to get service as TerminalService3|
|0.41396||Query 4 enters|
|0.41396||Query 4 requests 1 of Terminal4|
|0.438535||SystemScanner holds for 0.0397|
|0.478235||SystemScanner releases 1 of Buffer|
|0.478235||SystemScanner resumes Query 1|
|0.478235||SystemScanner holds for 0.0027|
|0.478235||Query 1 got served as TerminalService1|
|0.478235||Query 1 holds for 0.740207|
|0.480935||SystemScanner holds for 0.0027|
|0.483635||SystemScanner holds for 0.0027|
|0.486335||SystemScanner holds for 0.0027|
|0.489035||SystemScanner wants to give service as TerminalService3|
|0.489035||SystemScanner can serve as TerminalService3|
|0.489035||SystemScanner requests 1 of Buffer|
|0.489035||SystemScanner obtained 1 of Buffer|
|0.489035||SystemScanner holds for 0.0117|
|0.500735||SystemScanner holds for 0.0515655|
|0.552301||SystemScanner holds for 0.0397|
|0.576394||Query 3 wants to get service as TerminalService4|
|0.592001||SystemScanner releases 1 of Buffer|
|0.592001||SystemScanner resumes Query 2|
|0.592001||SystemScanner holds for 0.0027|
|0.592001||Query 2 got served as TerminalService3|
|0.592001||Query 2 holds for 0.655313|
For more examples to try, see the Birtwistle book.