Smalltalk80LanguageImplementation:Chapter 25

From 흡혈양파의 번역工房
Jump to navigation Jump to search
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.

class name ResourceCoordinator
superclass Resource
instance variable names whoIsWaiting
instance methods
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.

class name CarWash
superclass Simulation
instance methods
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.

class name Washer
superclass SimulationObject
instance methods
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.

class name Wash
superclass SimulationObject
instance variable names service
instance methods
accessing
    wants: aService
        service includes: aService
simulation control
    initialize
        super initialize.
        service  #('wash')
    tasks
        self produceResource: 'CarCustomer'
class name WashAndWax
superclass Wash
instance methods
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).

class name FerrySimulation
superclass Simulation
instance methods
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'.

class name Ferry
superclass SimulationObject
instance methods
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.

class name Truck
superclass SimulationObject
instance methods
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 enters
420.0 Ferry wants to serve for TruckCrossing
420.0 Truck enters
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.

class name Ferry
superclass SimulationObject
instance variable names currentSide
instance methods
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.

class name FerrySimulation
superclass Simulation
instance methods
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.

class name MainlandArrival
superclass SimulationObject
instance methods
simulation control
    tasks
        self produce: 1 ofResource: 'Mainland'
class name IslandArrival
superclass SimulationObject
instance methods
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.

class name Ferry
superclass SimulationObject
instance variable names currentSide
carsOnBoard
instance methods
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 name BankSimulation
superclass Simulation
class variable names Hour
instance methods
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.

class name BankCustomer
superclass SimulationObject
instance methods
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.

class name BankTeller
superclass SimulationObject
instance methods
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.

class name LunchtimeTeller
superclass BankTeller
instance variable names getDone
instance methods
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.

class name InformationSystem
superclass Simulation
instance methods
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.

class name Query
superclass SimulationObject
instance methods
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.

class name SystemScanner
superclass SimulationObject
instance variable names n
instance methods
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 enters
0.0 SystemScanner holds for 0.0027
0.0027 SystemScanner holds for 0.0027
0.0054 SystemScanner holds for 0.0027


...etc...

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


...etc...

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


...etc...

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


...etc...


For more examples to try, see the Birtwistle book.


Notes