Smalltalk80LanguageImplementationKor:Chapter 25
- 제 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의 서적을 참고하면 더 많은 예제를 확인할 수 있다.