멀티 쓰레드 프로그래밍을 할 때 큐와 쓰레드를 혼합해 사용하면 비교적 간단하게 문제를 해결할 수 있다. 작업 큐를 만들어 처리할 작업들을 큐에 저장하고 각 쓰레드는 큐에서 작업들을 하나씩 가져와 처리하는 방식이 일반적이다. 파이썬에서는 이를 위해 멀티 쓰레드 환경에서 사용할 수 있는 Queue
를 제공한다.
Queue의 핵심 함수는 아래 3개가 있다. 이외에도 qsize()
, empty()
, full()
이 있는데 적절히 사용하면 된다.
- put/get : 큐에 데이터를 삽입/삭제한다.
- task_done : 큐에서 작업이 완료되었다.
- join : 큐의 모든 작업이 끝날때까지 대기한다.
put/get은 일반적인 큐와 동일하게 데이터를 삽입하고 가져오는 함수이다. 그 이외 task_done
과 join
은 기존 큐에서는 볼 수 없는 함수이다. 여기서는 작업(Task)라는 용어가 나왔다. 코드를 통해 두 함수가 어떤 역할을 하는지 살펴본다.
먼저 join의 코드는 아래와 같다.
def join(self):
with self.all_tasks_done:
while self.unfinished_tasks:
self.all_tasks_done.wait()
self.unfinished_tasks
는 0으로 초기화되는 정수형 멤버 변수이다. join에서는 이 변수가 0이 될 때 까지 대기하는 동작을 한다. put
의 코드를 보면
xdef put(self, item, block=True, timeout=None):
...
self._put(item)
self.unfinished_tasks += 1
큐에 데이터가 삽입될 때 마다 self.unfinished_tasks
를 1 증가시킨다. 큐에 저장된 데이터의 갯수를 나타내는 변수 같지만 get 함수 정의에는 self.unfinished_tasks -= 1
처럼 감소시키는 코드가 없다. task_done
함수에서 감소 코드가 실행된다.
xxxxxxxxxx
def task_done(self):
with self.all_tasks_done:
unfinished = self.unfinished_tasks - 1
if unfinished <= 0:
if unfinished < 0:
raise ValueError('task_done() called too many times')
self.all_tasks_done.notify_all()
self.unfinished_tasks = unfinished
이처럼 작성된 이유를 생각해 봤다. Queue
를 사용할 때 데이터를 큐에 넣고, 큐에 저장된 데이터를 가져와 처리하는 순으로 동작한다. 즉 데이터를 가져오기 위해서 get
을 호출한 다음 가져온 데이터에 대한 작업을 수행할 것이다. 따라서 작업이 완료된 시점에 task_done
을 명시적으로 호출시켜 큐에서 가져온 데이터에 대한 작업이 완료됨을 알려준다. 이를 통해 큐에서도 작업이 완료됨을 알 수 있고 join
으로 종료 시점을 설정할 수 있다.