Ruby에서 큐(Queue)란 무엇인가?
큐(queue)는 쉽게 말해 대기 목록입니다.
최신 애플 제품을 구매하기 위해 줄을 서 있는 모습, 영화 티켓을 끊기 위해 기다리는 모습, 마트 계산대에서 차례를 기다리는 상황을 떠올려 보세요.
이것이 바로 큐입니다!
그리고 이러한 큐는 Ruby 프로젝트에서도 충분히 활용할 수 있습니다.
큐는 언제 유용할까?
큐는 도착한 순서대로 작업을 처리할 수 있게 해줍니다. 따라서 오래 기다린 항목일수록 우선순위를 받아야 하는 모든 상황에 적합합니다.
대표적인 활용 사례는 다음과 같습니다:
- 실제 세계의 대기열을 소프트웨어로 모델링
- 너비 우선 탐색(BFS)과 같은 알고리즘 구현
- CPU, 디스크, 프린터 등 공유 자원에 대한 접근 제어
자, 이제 코드를 살펴보겠습니다!
Ruby에서 큐 구현하기: 배열 활용
적절한 메서드만 사용한다면 일반 배열도 Queue처럼 동작하게 만들 수 있습니다.
핵심 메서드는 두 가지입니다:
unshift(Ruby 2.5 이상에서는prepend)pop
unshift를 호출하면 큐에 항목 하나를 추가합니다.
예제:
queue = [] queue.unshift "apple" queue.unshift "orange" queue.unshift "banana" # ["banana", "orange", "apple"]
pop을 호출하면 큐에서 가장 먼저 들어온 항목이 제거됩니다. 이 항목이 바로 다음으로 처리해야 할 대상입니다.
예제:
queue.pop # "apple" queue.pop # "orange"
항목을 제거하지 않고 다음 차례가 무엇인지 확인만 하고 싶다면 마지막 요소를 조회하면 됩니다.
예제:
queue[-1] # "banana"
이런 연산을 peek(살짝 들여다보기)라고 부릅니다.
스레드에 안전한 Ruby 동시성 큐
Ruby는 스레드에 안전(thread-safe)하고 블로킹 방식으로 동작하는 Queue 클래스를 기본으로 제공합니다. 멀티스레드 프로그램에서 작업을 조율할 때 이 클래스를 활용할 수 있습니다.
예제:
que = Queue.new que << 1 que << 2 que << 3
pop으로 큐에서 항목을 꺼낼 수 있습니다:
que.pop # 1 que.pop # 2
여기서 중요한 특징이 하나 있습니다. 큐가 비어 있는 상태에서 pop을 호출하면 현재 스레드가 잠들면서 새 항목이 추가될 때까지 대기합니다. 이것이 바로 "블로킹(blocking)"의 의미입니다.
블로킹 없이 즉시 결과를 받고 싶다면 pop에 true를 전달하세요:
que.pop(true)
이 경우 큐가 비어 있으면 ThreadError: queue empty 예외가 발생합니다.
SizedQueue 사용 방법
SizedQueue는 일반 큐와 동일하지만 크기 제한이 있다는 점이 다릅니다.
예제:
que = SizedQueue.new(5)
큐가 가득 찬 상태에서 push(<<와 동일)를 호출하면, 현재 스레드가 일시 중단되고 큐에서 항목이 빠져나갈 때까지 대기합니다.
예제:
que.push(:bacon)
대기하는 대신 예외를 발생시키고 싶다면 두 번째 인자로 true를 전달하세요:
que.push(:bacon, true)
이 경우 ThreadError: queue full 예외가 발생합니다.
영상 튜토리얼
마무리
지금까지 Ruby에서 큐를 사용하는 방법을 알아봤습니다!
작업을 FIFO(선입선출, First-In-First-Out) 순서로 처리해야 한다면 큐가 정답입니다. 큐를 구현하는 방법은 두 가지로, 배열을 직접 활용하거나 내장된 Queue 클래스를 사용하는 것입니다.
읽어주셔서 감사합니다. 🙂