Smalltalk80LanguageImplementationKor:Chapter 25

From 흡혈양파의 번역工房
Jump to navigation Jump to search
제 25 장 이벤트 주도 시뮬레이션을 위해 조절된 자원

이벤트 주도 시뮬레이션을 위해 조절된 자원

세 가지 유형의 시뮬레이션 객체, 즉 소모 가능, 소모 불가, 재생 가능한 객체들은 수량화 가능한 정적 자원에 대한 접근성을 조정한다. 조정은 두 개 또는 그 이상의 시뮬레이션 객체의 작업을 동기화하는 데에도 필요하다. 예를 들어, 세차장은 자동차가 세차장에 나타날 때에만 작업을 실행하고, 은행 직원은 고객이 은행에 나타나야지만 서비스를 제공한다.


두 개 또는 그 이상의 SimulationObjects에 동기화를 제공하기 위한 매커니즘은 ResourceCoordinator 클래스가 지원한다. ResourceCoordinator 클래스는 Resource 클래스의 구체적 서브클래스로, Resource 클래스는 24장에서 정의한 바 있다. 이번 장은 ResourceCoordinator의 구현을 설명하고, 이러한 동기화 기법을 이용하는 예제를 몇 가지 제시하는 데 목적이 있다.


ResourceCoordinator 클래스의 구현

ResourceCoordinator는 다른 SimulationObject의 작업과 동기화되어야 하는 작업을 가진 SimulationObject를 나타낸다. 객체 중 하나는 자원 또는 "customer"(손님)로 간주하고, 또 다른 객체는 서비스를 제공하기 위해 이러한 자원을 필요로 하는 "server"(종업원) 또는 점원으로 생각할 수 있겠다. 주어진 시간에 손님이 종업원을 기다릴 수도 있고 종업원이 손님을 기다릴 수도 있으며 기다리는 사람이 없을 수도 있다. 하나의 큐만 유지되어야 하는데, ResourceCoordinator의 변수는 큐가 손님, 종업원을 포함하는지 혹은 비어 있는지를 추적한다. Resource 슈퍼클래스로부터 상속된 pending 변수는 큐를 나타내고, wholsWaiting 변수는 큐의 상태를 나타낸다.


ResourceCoordinator로 만들 수 있는 질의가 세 가지 있는데, '기다리는 손님이 있는가?(customersWaiting)', '기다리는 종업원이 있는가?(serversWaiting)', '몇 명이 기다리고 있는가?(queueLength)'가 되겠다. acquire 메시지는 SimulationObject에서 비롯되는데, 이는 서비스를 제공할 손님을 획득하고 싶은 종업원의 역할을 한다. 손님이 기다리는 경우 SimulationObject는 손님에게 서비스를 제공할 수 있고 (giveService), 기다리는 손님이 없다면 기다리는 서버의 큐로 설정된 큐로 SimulationObject가 추가된다. producedBy: aCustomer 메시지는 SimulationObject에서 비롯되고, 서비스를 받길 원하는 고객의 역할을 한다. 종업원이 기다린다면 SimulationObject는 서비스를 받을 수 있고 (getServiceFor: aCustomerRequest), 그렇지 않으면 대기 중인 손님의 큐로 설정된 큐에 SimulationObject가 추가된다.


어떤 경우든 큐는 DelayedEvent의 인스턴스들로 구성된다. 큐가 손님으로 구성된다면 DelayedEvent 상태는 서비스를 받길 기다리는 SimulationObject가 된다. 큐가 종업원으로 구성된 경우, 손님을 얻을 때까지 DelayedEvent는 nil이 되고 그 시점에서 상태는 손님 요청(이는 손님 요청이 처음으로 이루어질 때 저장된 DelayedEvent이다)으로 설정된다. DelayedEvent가 재개되면 상태는 요청자에게 리턴되는데, 이렇게 종업원은 손님 요청으로 접근성을 얻는다. 동기화된 작업이 완료되면 종업원은 요청을 재개하여 손님의 활동을 재개할 수 있다.

클래스명 ResourceCoordinator
슈퍼클래스 Resource
인스턴스 변수명 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


종업원이 손님에게 서비스를 제공하면 손님 요청은 보류(pause)되고 시뮬레이션 프로세스 참조 계수는 서비스 작업이 재개될 때까지 감소되어야 함을 주목한다.


예제: 세차장 시뮬레이션

CarWash 시뮬레이션 예제는 도착하여 세차 또는 세차와 광택을 요청하는 자동차로 구성된다. Washers는 세차와 광택에 이용할 수 있고, 서비스를 제공할 차량이 없는 경우 Washers는 idle 상태가 된다. CarWash 시뮬레이션의 정의는 다음과 같다. 다양한 자동차 손님에 대해 한 명의 자원 조정자가 있다. 자동차는 20분마다 한 대씩 세차장에 도착하고, 세차와 광택을 동시에 원하는 자동차는 30분마다 한 대씩 도착한다. Washer는 CarWash가 처음으로 시작될 때 도착하여 시뮬레이션이 진행되는 동안 계속 머문다.

클래스명 CarWash
슈퍼클래스 Simulation
인스턴스 메서드
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'


자동차 손님 유형마다 필요로 하는 서비스를 보고할 수 있다. Washer 작업은 각 자동차 손님이 원하는 서비스 유형에 따라 좌우된다. 먼저 손님을 얻는다. 그리고 주어진 서비스가 제공되고 (세차나 광택 또는 둘 다), 손님 고유의 활동이 계속된다.

클래스명 Washer
슈퍼클래스 SimulationObject
인스턴스 메서드
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]


차량 Wash와 WashAndWax가 다음으로 정의된다. 각각은 필요로 하는 서비스 유형을 정의하는 속성을 포함한다. 이러한 차량의 tasks는 서비스를 요청하고 서비스를 제공받으면 떠나는 것으로 구성된다.

클래스명 Wash
슈퍼클래스 SimulationObject
인스턴스 변수명 service
인스턴스 메서드
accessing
    wants: aService
        service includes: aService
simulation control
    initialize
        super initialize.
        service  #('wash')
    tasks
        self produceResource: 'CarCustomer'
클래스명 WashAndWax
슈퍼클래스 Wash
인스턴스 메서드
simulation control
    initialize
        super initialize.
        service  #('wash' 'wax')


WashAndWax는 Wash의 서브클래스로서 정의되는데, 둘의 유일한 차이는 서비스 속성의 설정에 있기 때문이다. Wash를 EventMonitor의 서브클래스로 만들어 아래의 trace가 생성되었다.

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


78.2874에서 12명의 손님이 기다리고 있으며, 그 중 8명은 Wash, 4명은 WashAndWax이고, 2명의 Wash와 2명의 WashAndWax는 이미 서비스를 제공받았다.


이러한 trace로부터 우리는 해당 CarWash의 수요를 충족시키기 위해서는 Washers를 더 많이 필요로 한다는 사실을 알 수 있다. 손님이 기다리는 시간과 (앞장에서 소개한 처리량 히스토그램과 기간 통계 수집 기법을 이용해) 직원이 바쁘거나 idle 상태에 있는 시간 비율에 관한 구체적인 데이터를 수집하는 것도 가능하다.


예제: 특수 트럭을 위한 선착장 서비스

24장에서 본토와 섬을 이동하며 승용차를 실어 나르는 선착장 서비스 예제를 마지막으로 소개하였다. 자동차는 페리가 획득할 수 있는 정적 자원으로 모델화되었다. 페리의 작업은 최대 6대의 자동차를 적재하고 물을 건너 싣고 왔던 자동차를 하선시킨 후 그러한 과정을 반복하는 것이다. 예제는 Graham Birtwistle이 저서에서 소개한 Demos의 예제와 비슷하다. 본문에서 Birtwistle은 페리 서비스를 트럭 이동이 가능한 페리 서비스의 조정이라고 설명한다. 트럭은 배달을 위해 본토에서 섬으로 이동하고, 더 많은 보급품을 얻기 위해 본토로 돌아온 후 다시 섬으로 이동한다. 페리는 트럭을 싣고 있을 경우 한 방향으로만 이동이 가능하다. 이러한 페리 시뮬레이션 버전은 페리와 트럭을 나타내는 SimulationObjects의 조정이 필요한데, 각자가 해야 할 작업을 갖고 있지만 트럭은 페리의 도움이 없이는 자신의 작업을 수행할 수 없고 페리는 이동해야 할 트럭이 없다면 수행해야 할 작업이 사라진다.


페리 서비스는 7:00 a.m.에 시작하여 (일일 중 420.0분) 7:00 p.m.에 (일일 중 1140분) 끝난다.

클래스명 FerrySimulation
슈퍼클래스 Simulation
인스턴스 메서드
initialization
    defineArrivalSchedule
        self scheduleArrivalOf: Truck new at: 420.0.
        self scheduleArrivalOf: Ferry new at: 420.0.
    defineResources
        self coordinate: 'TruckCrossing'


Truck과 Ferry는 'TruckCrossing' 자원의 생산 및 획득과 관련해 정의된다.

클래스명 Ferry
슈퍼클래스 SimulationObject
인스턴스 메서드
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


Truck은 섬에 보급품을 전달하고 본토에서 보급품을 적재한다.

클래스명 Truck
슈퍼클래스 SimulationObject
인스턴스 메서드
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


Truck 또는 Ferry의 정의에서 특정 측면, 본토나 섬을 검사하지 않는 이유는 두 시뮬레이션 객체 모두 동일한 측면(본토)에서 시작하고 교차 시 동기화는 둘 다 동일한 측면에 있도록 보장하는 것으로 가정하기 때문이다. FerrySimulation을 실행하기 위한 이벤트의 trace는 다음과 같다.

  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


페리 작업은 저녁에 휴식 장소가 꼭 본토측이 되도록 보장하지는 않는다. 이는 페리가 어느 측면에 있는지 감시하고 본토로 돌아온 후에 중단시켜야만 가능하다. Truck 측면은 Ferry와 동기화되므로 Ferry의 정의만 변경되어야 한다.

클래스명 Ferry
슈퍼클래스 SimulationObject
인스턴스 변수명 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']


물을 건너야 하는 자동차가 페리 램프에 도착할 수 있다고 가정하자. 페리는 트럭 외에 4대의 승용차를 실을 수 있지만 트럭이 없다면 운행하지 않을 것이다. 그렇다면 본토 또는 섬에 도착하는 승용차를 더함으로써 시뮬레이션의 정의를 변경할 수 있는데, 승용차는 24장에서와 똑같이 정적 자원으로 도입하겠다.

클래스명 FerrySimulation
슈퍼클래스 Simulation
인스턴스 메서드
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'


MainlandArrival과 Island Arrival에 대한 정의는 24장에 제공된 정의와 동일하다.

클래스명 MainlandArrival
슈퍼클래스 SimulationObject
인스턴스 메서드
simulation control
    tasks
        self produce: 1 ofResource: 'Mainland'
클래스명 IslandArrival
슈퍼클래스 SimulationObject
인스턴스 메서드
simulation control
    tasks
        self produce: 1 ofResource: 'Island'


이제 Ferry는 현재 측면의 램프에 대기 중인 승용차가 있다면 그러한 승용차의 적재와 하선을 고려해야 한다.

클래스명 Ferry
슈퍼클래스 SimulationObject
인스턴스 변수명 currentSide
carsOnBoard
인스턴스 메서드
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']


예제: 은행

은행 직원은 손님이 은행 직원 창구에 나타나 서비스를 요청할 때만 작업을 수행한다. 은행 직원의 서비스는 손님의 수요에 따라 좌우되므로 은행 직원의 작업에 대한 명세는 손님으로부터 정보 요청을 포함한다.


은행이 하루 종일 근무할 은행 직원을 두 명 배정한다고 가정하자. 은행은 9:00 a.m.에 문을 열고 3:00 p.m. 에 닫는다. 하루 동안 5분의 표준 편차로 10분마다 한 명의 손님이 도착한다. 정오의 점심 시간에는 손님이 급속도로 증가하여 평균 3분마다 한 명씩 찾아온다. 증가하는 서비스 수요를 처리하기 위해 두 명의 은행 직원이 추가로 투입된다. 정규 은행 직원이 손님을 상대하지 않을 때는 처리해야 할 서류 작업이 있다.


손님과 정규 직원이 은행에 도착하는 것은 BankSimulation으로 defineArrivalSchedule 메시지를 전송하여 스케줄링된다. 점심 시간에 손님이 늘어나는 것은 점심 시간 손님이 나타나는 총 60분을 나타내는 20x3의 표본공간인 이산 확률 분포로 표현된다. 손님의 일반적 부하와 섞으면 이는 가장 바쁜 시간대에 20명 또는 그 이상의 손님이 나타난다는 의미다.


시뮬레이션 객체 BankTeller, LunchtimeTeller, BankCustomer, BankSimulation을 정의한다.

클래스명 BankSimulation
슈퍼클래스 Simulation
클래스변수명 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


ResourceCoordinator는 손님을 (서비스 수령자) 은행 직원과 (서비스 제공자) 매칭시킬 책임이 있다. 은행 손님의 작업은 단순하다. 은행에 들어가서 손님은 직원의 관심을 받고 서비스를 요청하면 된다. 서비스를 받은 후에는 떠나면 그만이다. 손님이 은행에서 소요한 시간은 손님이 은행 직원을 기다려야 하는 시간과 은행 직원이 서비스를 제공하는 시간에 따라 좌우된다.

클래스명 BankCustomer
슈퍼클래스 SimulationObject
인스턴스 메서드
simulation control
    tasks
        self produceResource: 'TellerCustomer'


은행 직원의 작업은 손님의 요구에 따라 달려 있다. 이 예제를 간단히 유지하기 위해 우리는 BankTeller가 모든 손님마다 똑같은 작업을 처리하여 2-15분의 시간이 소요되는 것으로 가정할 것이다. 아니면 세차장 예제에서와 같이 원하는 서비스의 목록을 각 BankCustomer에게 제공하는 방법이 있으며, Bankteller는 집합을 열거하여 각 서비스 유형에 적절한 시간을 소요한다. 손님이 없을 때마다 은행 직원은 다른 작업을 수행한다. 이러한 작업은 작으면서 짧은 시간을 소비하지만 은행 직원은 방해받지 않는 것으로 정의한다. 이러한 배경 서류 작업이 완료될 때마다 은행 직원은 손님이 도착하여 서비스를 기다리고 있는지 확인한다.

클래스명 BankTeller
슈퍼클래스 SimulationObject
인스턴스 메서드
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


LunchtimeTeller는 1시간이 지나면 은행을 떠나도록 스스로를 스케줄링해야 한다.


이러한 스케줄링은 initialize에 대한 응답에 명시할 수 있다. 떠날 시간이 되면 LunchtimeTeller는 떠나기 전에 작업이 모두 완료되도록 확보해야 한다. 따라서 시그널(getDone)은 finishUp 메시지가 처음으로 전송될 때 true로 설정되는데, 이러한 시그널은 은행 직원의 카운터 작업이 완료될 때마다 확인된다. 두 번째로 finishUp이 전송되면 시그널은 true가 되고 은행 직원이 떠날 수 있다. LunchtimeTeller는 정규 BankTeller와 다르게 손님이 없더라도 서류 작업을 하지 않는다.

클래스명 LunchtimeTeller
슈퍼클래스 BankTeller
인스턴스 변수명 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


이벤트의 부분적 trace는 다음과 같다.

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


... 그리고 이는 추가 지원이 도착하는 점심 시간까지 계속되는데, 점심 시간이 되면 18명의 손님이 들어오고, BankTeller first는 BankCustomer 18 서비스를 제공한다...

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이 방금 떠나고 점심 시간이 끝나 은행에는 3명의 다른 손님들이 남아 있고, 현재 손님을 끝마치면 LunchtimeTellers는 떠날 것이다...

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


위와 같은 식으로 계속된다...


여기서 수집한 데이터는 은행 직원의 busy/idle 비율과 손님의 평균 대기 시간을 포함한다.


예제: 정보 시스템

마지막으로 소개할 예제도 Birtwistle 서적에 실려 있는데, Birtwistle의 말에 따르면 이는 GPSS와 같은 시뮬레이션 시스템에서 자주 사용되는 예제라고 한다. 예제는 바로 사용자가 도착하여 검색 요청을 할 수 있는 원격 단말기를 설명하는 정보 시스템 시뮬레이션이다. 쿼리를 가진 손님이 둘 중 하나의 단말기에 도착하면 필요 시 그것을 사용하도록 큐를 한다. 시스템 스캐너가 단말기로부터 단말기로 회전하여 요청이 기다리고 있는지 확인하고, 기다리고 있을 경우 서비스를 제공한다. 서비스라 함은 스캐너가 세 개의 쿼리를 동시에 보유할 수 있는 버퍼 장치로 쿼리를 복사하는 것을 의미하고, 이용할 수 있는 버퍼 위치가 없는 경우 하나를 이용할 수 있을 때까지 복사 프로세스가 기다려야 한다. 성공적으로 버퍼에 복사가 이루어지면 시스템은 쿼리를 처리하고, 스캐너를 다시 필요로 하지 않고 단말기로 리턴하기 위해 버퍼에 답을 위치시킨다.


Birtwistle이 제공한 데이터를 이용해 우리는 6개의 단말기가 있는 시스템을 모델화하고자 한다. 손님은 5분의 지수 평균 시간을 가진 단말기에 도착한다. 버퍼는 정적 자원으로 모델화되고, 단말기 또한 정적 자원으로 모델화되지만 단말기 서비스는 쿼리의 작업과 조정되는 작업을 가진 객체에 해당한다.

클래스명 InformationSystem
슈퍼클래스 Simulation
인스턴스 메서드
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]


위의 메서드에서 우리는 6개 단말기의 속성명을 정적 자원으로 구성하고 6개 단말기 서비스를 조정된 서비스로 형성하기 위해 문자열 결합(string concatenation)을 사용하는데, 이름은 Terminal0, …, Terminal5와 TerminalService 0, …, TerminalService5 식으로 되어 있다.


6개 단말기마다 단말기 서비스에 대한 ResourceCoordinators는 은행 시뮬레이션이나 세차장 시뮬레이션 예제와 다르게 처리된다. 손님 또는 종업원의 큐는 언제든 하나의 요소만 포함하거나 어떤 요소도 포함하지 않는다. 시뮬레이션 동안 Query는 단말기 서비스를 기다리기 위해 큐로 들어갈 것이다. SystemScanner는 조정자에서 조정자로, round-robin 방식으로 이동하여 Query가 기다릴 경우 서비스 제공자의 역할을 한다.


손님 Query는 먼저 응답을 받기 위해 단말기 자원으로 접근해야 한다. 6개 단말기 중 하나로 접근 시 손님이 요청을 키 인(key in)하고 응답을 기다린다. 쿼리를 입력하기까지는 0.3분에서 0.5분이 소요된다 (균일 분포). 이후 단말기 서비스가 요청된다. 이제 쿼리는 SystemScanner를 기다리는데, SystemScanner가 기다리는 쿼리를 인식하면 필요한 서비스를 제공한다. 이는 SystemScanner가 쿼리에 대한 버퍼 슬롯을 획득하고 요청을 버퍼로 복사함을 의미한다. 쿼리를 버퍼로 전송하는 데에는 0.0117 분이 소요된다. 이제 응답은 단말기로 전송되어 읽을 수 있고, 버퍼는 자유화(free up)할 수 있으며, 단말기는 해제된다. 쿼리의 처리에는 0.05에서 0.10분이 소요되고 (균일 분포), 응답을 단말기로 다시 전송하는 데에는 0.0397분이 소요된다. 손님이 응답을 읽기까지는 0.6에서 0.8분이 소요된다 (균일 분포).

클래스명 Query
슈퍼클래스 SimulationObject
인스턴스 메서드
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


스캐너가 할 일은 단말기에서 단말기로 회전하여 요청이 보류되어 있는지 확인하고, 보류된 요청이 있다면 쿼리를 버퍼로 전송하기까지 기다렸다가 넘어가는 일이다. 스캐너 회전에는 0.0027분이 소요되고 단말기의 검사에도 동일한 시간이 소요된다.

클래스명 SystemScanner
슈퍼클래스 SimulationObject
인스턴스 변수명 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]]


기다리는 Query가 없다면 SystemScanner는 idle 상태에 있지 않으며, 계속해서 단말기에서 단말기로 이동하면서 Query를 검사한다. 이러한 이동은 Query가 발견되면 중단되어 서비스를 제공한다. 서비스가 완료되면 SystemScanner는 다시 순환으로 돌아가 다른 Query를 찾는다.


처음에는 그다지 많은 일이 일어나지 않는다. 첫 Query가 들어오지만 Terminal 1에서 쿼리를 입력하기 위해 0.360428 시간 단위 동안 보유하고, 그 동안 SystemScanner는 단말기 주위로 이동한다. 두 번째 Query가 0.0472472에 들어와 Terminal3을 요청하고, 세 번째는 0.130608에 들어와 Terminal4를 요청한다. 0.360428에서 첫 번째 Query가 Terminal1에서 서비스를 요청하고, 0.367198 시간에서 약간의 시간이 주어진다. 이 시간과 0.478235 사이에 SystemScanner는 Butter를 얻어 쿼리를 Buffer로 복사, 처리하고 응답을 전송한 다음 Buffer를 해제하여 Query를 재개함으로써 서비스를 제공한다. 그 동안 두 번째 Query가 서비스를 요청하였고, 네 번째 Query가 Terminal4에 들어왔다. SystemScanner는 Terminal3으로 회전하여 그곳에서 기다리는 두 번째 Query에게 서비스를 제공한다.

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


...이와 같은 방식으로 계속된다...

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


...이와 같은 방식으로 계속된다...


Birtwistle의 서적을 참고하면 더 많은 예제를 확인할 수 있다.


Notes