원래 전문가를 위한 파이썬에 있던 예제이고 취향에 따라 조금 수정되었습니다
코루틴들과 이를 돌리는 이벤트 루프가 어떻게 상호작용하는지를 맛볼 수 있는 것 같네요
이벤트 루프에 이벤트들에 적용시킬 루프 타임이 존재합니다
(실제 시간이 아닌 스텝)
이벤트 루프가 우선순위 큐에서 이벤트 타임 기준으로 이벤트를 꺼낸 후
이 이벤트를 실행합니다
그리고 이 이벤트를 보내왔던 코루틴에 다음 이벤트 타임을 계산하여 send하면
코루틴이 이 이벤트 타임이 적용된 다음 이벤트를 yield하고
이벤트 루프가 이 이벤트를 우선순위 큐에 넣는 것이 핵심입니다
(참고로 밑에 EventType의 interval은 그대로 적용되는게 아니라 이를 이용하여 재계산됩니다)
-- 소스
import collections
import queue
import random
import time
EventType = collections.namedtuple('EventType', 'priority message interval')
event_types = {
'start': EventType(1, '차고 출발', 10),
'pick': EventType(2, '승객 탑승', 20),
'drop': EventType(3, '승객 하차', 10),
'end': EventType(4, '차고 귀환', 0)
}
class Event:
def __init__(self, taxi_id, event_time, event_type):
self.taxi_id = taxi_id
self.time = event_time
self.type = event_type
def __lt__(self, other):
if not self.time == other.time:
return self.time < other.time
return self.taxi_id < other.taxi_id
def __repr__(self):
return '시각 {:03d}:'.format(self.time) +
' ' * (self.taxi_id + 1) +
'택시 {} - {}'.format(self.taxi_id+1, self.type.message)
class EventLoop:
def __init__(self, taxies):
self.taxies = taxies
self.events = queue.PriorityQueue()
def run_until_complete(self):
loop_time = 0
for _, taxi in self.taxies.items():
self.events.put(next(taxi))
while True:
if self.events.empty():
break
event = self.events.get()
print(event)
taxi = self.taxies[event.taxi_id]
loop_time = event.time
next_time = loop_time + int(random.random() * event.type.interval) + 1
end_time = loop_time + 1
try:
next_event = taxi.send((next_time, end_time))
except StopIteration:
del(self.taxies[event.taxi_id])
else:
self.events.put(next_event)
def taxi(taxi_id, trips_num, start_time):
event_time, _ = yield Event(taxi_id, start_time, event_types['start'])
for _ in range(trips_num):
event_time, _ = yield Event(taxi_id, event_time, event_types['pick'])
event_time, end_time = yield Event(taxi_id, event_time, event_types['drop'])
yield Event(taxi_id, end_time, event_types['end'])
def main(taxi_numbers=3, trips_max=5, start_interval=5):
taxies = {i: taxi(i, random.randint(1, trips_max), i*start_interval)
for i in range(taxi_numbers)}
sim = EventLoop(taxies)
sim.run_until_complete()
if __name__ == '__main__':
main()
-- 출력 예시(실행시마다 다름)
시각 000: 택시 1 - 차고 출발
시각 005: 택시 2 - 차고 출발
시각 006: 택시 2 - 승객 탑승
시각 009: 택시 2 - 승객 하차
시각 010: 택시 1 - 승객 탑승
시각 010: 택시 3 - 차고 출발
시각 016: 택시 1 - 승객 하차
시각 017: 택시 3 - 승객 탑승
시각 019: 택시 2 - 승객 탑승
시각 024: 택시 1 - 승객 탑승
시각 027: 택시 3 - 승객 하차
시각 028: 택시 3 - 승객 탑승
시각 031: 택시 3 - 승객 하차
시각 033: 택시 3 - 승객 탑승
시각 035: 택시 1 - 승객 하차
시각 037: 택시 3 - 승객 하차
시각 038: 택시 2 - 승객 하차
시각 038: 택시 3 - 차고 귀환
시각 040: 택시 1 - 승객 탑승
시각 041: 택시 2 - 승객 탑승
시각 044: 택시 1 - 승객 하차
시각 045: 택시 1 - 차고 귀환
시각 053: 택시 2 - 승객 하차
시각 058: 택시 2 - 승객 탑승
시각 059: 택시 2 - 승객 하차
시각 060: 택시 2 - 차고 귀환
책에 나오는 원래 예제에서는 마지막 승객을 하차시키고 차고에 돌아가는 사이 시간 처리가 세심하지 못해서 어색했는데, 이 부분도 수정하셨네요. 굿굿입니다.