이벤트 루프는 여러 시스템과 언어에서 중요한 패턴으로 사용됩니다. 각 환경마다 구현 방식이 다르지만 개념적으로는 유사합니다. 아래에 몇 가지 유명한 이벤트 루프 구현 사례를 소개합니다.
1. Node.js의 libuv 이벤트 루프
Node.js의 이벤트 루프는 libuv 라이브러리에 의해 구현되며, C로 작성되어 있습니다. 다음은 libuv의 핵심 이벤트 루프 코드입니다:
int uv_run(uv_loop_t* loop, uv_run_mode mode) {
int timeout;
int r;
int ran_pending;
r = uv__loop_alive(loop);
if (!r)
uv__update_time(loop);
while (r != 0 && loop->stop_flag == 0) {
uv__update_time(loop);
uv__run_timers(loop);
ran_pending = uv__run_pending(loop);
uv__run_idle(loop);
uv__run_prepare(loop);
timeout = 0;
if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
timeout = uv_backend_timeout(loop);
uv__io_poll(loop, timeout);
uv__run_check(loop);
uv__run_closing_handles(loop);
if (mode == UV_RUN_ONCE) {
uv__update_time(loop);
uv__run_timers(loop);
}
r = uv__loop_alive(loop);
if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
break;
}
return r;
}
이 코드는 Node.js의 비동기 I/O 모델의 핵심이며, 여러 단계(phases)로 이벤트 루프를 실행합니다.
2. Python의 asyncio 이벤트 루프
Python 3.4부터 도입된 asyncio 라이브러리의 이벤트 루프 구현 일부입니다:
def run_forever(self):
"""Run the event loop until stop() is called."""
self._check_closed()
self._thread_id = threading.get_ident()
old_agen_hooks = sys.get_asyncgen_hooks()
sys.set_asyncgen_hooks(firstiter=self._asyncgen_firstiter_hook,
finalizer=self._asyncgen_finalizer_hook)
try:
events._set_running_loop(self)
while True:
self._run_once()
if self._stopping:
break
finally:
self._stopping = False
events._set_running_loop(None)
sys.set_asyncgen_hooks(*old_agen_hooks)
def _run_once(self):
"""Run one full iteration of the event loop.
This calls all currently ready callbacks, polls for I/O,
schedules the resulting callbacks, and finally schedules
'call_later' callbacks.
"""
sched_count = len(self._scheduled)
if (sched_count > _MIN_SCHEDULED_TIMER_HANDLES and
self._timer_cancelled_count / sched_count >
_MIN_CANCELLED_TIMER_HANDLES_FRACTION):
# Remove delayed calls that were cancelled
new_scheduled = []
for handle in self._scheduled:
if handle._cancelled:
handle._scheduled = False
else:
new_scheduled.append(handle)
heapq.heapify(new_scheduled)
self._scheduled = new_scheduled
self._timer_cancelled_count = 0
else:
# Remove delayed calls that were cancelled from head of queue
while self._scheduled and self._scheduled[0]._cancelled:
self._timer_cancelled_count -= 1
handle = heapq.heappop(self._scheduled)
handle._scheduled = False
timeout = None
if self._ready or self._stopping:
timeout = 0
elif self._scheduled:
# Compute the desired timeout
when = self._scheduled[0]._when
timeout = max(0, when - self.time())
# ... (이벤트 폴링 및 콜백 처리 코드)
# 타이머 이벤트 처리
end_time = self.time() + self._clock_resolution
while self._scheduled:
handle = self._scheduled[0]
if handle._when > end_time:
break
handle = heapq.heappop(self._scheduled)
handle._scheduled = False
self._ready.append(handle)
# 준비된 콜백 실행
ntodo = len(self._ready)
for i in range(ntodo):
handle = self._ready.popleft()
if handle._cancelled:
continue
handle._run()
이 코드는 Python에서 비동기 프로그래밍을 가능하게 하는 핵심 구성 요소입니다.
3. FreeRTOS의 태스크 스케줄러
FreeRTOS는 임베디드 시스템을 위한 실시간 운영체제로, 이벤트 루프와 유사한 태스크 스케줄링 메커니즘을 사용합니다:
void vTaskStartScheduler( void )
{
/* 스케줄러 초기화 및 첫 번째 태스크 시작 */
if( xPortStartScheduler() != pdFALSE )
{
/* 스케줄러가 종료되면 여기로 돌아오지 않음 */
}
}
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
/* 시스템 틱 카운터 증가 */
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
/* 틱 카운트가 0이면 delayed 태스크 리스트 교체 */
if( xConstTickCount == ( TickType_t ) 0U )
{
taskSWITCH_DELAYED_LISTS();
}
else
{
/* 지연 만료된 태스크 확인 */
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ;; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
/* 지연 리스트가 비었으면 최대값으로 설정 */
xNextTaskUnblockTime = portMAX_DELAY;
break;
}
else
{
/* 리스트에서 첫 번째 항목 가져오기 */
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
/* 틱 카운트가 항목 값보다 작으면 만료된 태스크 없음 */
if( xConstTickCount < xItemValue )
{
xNextTaskUnblockTime = xItemValue;
break;
}
/* 태스크를 지연 리스트에서 준비 리스트로 이동 */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
prvAddTaskToReadyList( pxTCB );
/* 우선순위가 현재 실행 중인 태스크보다 높은지 확인 */
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
}
}
}
}
/* 태스크 실행 시간 트래킹 업데이트 */
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook();
}
#endif
return xSwitchRequired;
}
FreeRTOS의 스케줄러는 우선순위 기반으로 태스크를 관리하며, 실시간 시스템에 적합한 예측 가능한 동작을 제공합니다.
4. Java의 NIO Selector (이벤트 루프)
Java NIO는 논블로킹 I/O를 지원하며, Selector 클래스를 통해 이벤트 루프를 구현합니다:
public abstract class Selector implements Closeable {
// 셀렉터 생성
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
// 키 선택 메서드
public abstract int select() throws IOException;
public abstract int select(long timeout) throws IOException;
public abstract int selectNow() throws IOException;
public abstract Selector wakeup();
// 선택된 키 세트 반환
public abstract Set<SelectionKey> selectedKeys();
// 등록된 키 세트 반환
public abstract Set<SelectionKey> keys();
// 이벤트 루프 사용 예시
public static void eventLoop() throws IOException {
Selector selector = Selector.open();
// 채널 등록 코드...
while (true) {
// 이벤트 발생 대기
int readyChannels = selector.select();
if (readyChannels == 0) {
continue;
}
// 준비된 키 처리
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// 연결 수락 처리
} else if (key.isReadable()) {
// 읽기 처리
} else if (key.isWritable()) {
// 쓰기 처리
}
keyIterator.remove();
}
}
}
}
Java NIO Selector는 다중 채널에서 발생하는 I/O 이벤트를 효율적으로 감지하고 처리하는 메커니즘을 제공합니다.
5. JavaScript 브라우저의 이벤트 루프 (V8 엔진)
크롬 브라우저의 V8 엔진 내부 이벤트 루프 구현의 일부입니다:
void Isolate::RunMicrotasks() {
TRACE_EVENT0("v8", "V8.RunMicrotasks");
TRACE_EVENT_CALL_STATS_SCOPED(this, "v8", "V8.RunMicrotasks");
if (thread_local_top()->microtask_pending_) {
DCHECK(this->micro_task_queue_);
this->micro_task_queue_->RunMicrotasks(this);
}
}
void MicrotaskQueue::RunMicrotasks(Isolate* isolate) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.microtasks"),
"V8.RunMicrotasks");
MicrotasksScope::PerformCheckpoint(isolate);
}
void MicrotasksScope::PerformCheckpoint(Isolate* isolate) {
auto* microtask_queue = isolate->default_microtask_queue();
microtask_queue->PerformCheckpoint(isolate);
}
void MicrotaskQueue::PerformCheckpoint(Isolate* isolate) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.microtasks"),
"MicrotaskQueue::PerformCheckpoint");
if (!microtasks_policy_) return;
// Increase the microtask depth used by MicrotasksScopes.
int current_microtask_depth = depth_ + 1;
depth_ = current_microtask_depth;
// 마이크로태스크 처리
while (!capacity_ && pending_microtask_count_) {
Microtask task = data_[start_];
// 원형 큐 처리
start_ = (start_ + 1) % capacity_;
pending_microtask_count_--;
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.microtasks"),
"V8.RunMicrotask");
// 마이크로태스크 실행
task.Run(isolate);
if (depth_ != current_microtask_depth) {
// 실행 중에 새 마이크로태스크가 추가되었으므로 루프 계속
current_microtask_depth = depth_;
}
}
// 깊이 복원
depth_ = current_microtask_depth - 1;
// 매크로태스크 처리 (브라우저 내부 구현)
// ...
}
이 코드는 JavaScript 비동기 모델의 핵심인 마이크로태스크 큐를 처리하는 부분입니다.
각 구현은 특정 환경의 요구사항에 맞게 최적화되어 있지만, 공통적으로 이벤트를 수집하고 처리하는 루프 구조를 갖추고 있습니다. 이러한 이벤트 루프는 현대 소프트웨어 시스템의 반응성과 효율성을 높이는 핵심 메커니즘입니다.