Home » 여러 스레드의 시작 시점을 맞추기 – Barrier

여러 스레드의 시작 시점을 맞추기 – Barrier

Barrier는 동시성 프로그래밍에서 사용되는 동기화 수단 중 하나로 여러 워커(스레드)들을 특정한 시점까지 기다리게 한 후 한꺼번에 함께 시작하도록 해준다. 비슷한  방식의 동기화 프리미티브로 이벤트(Event)가 있는데, 이벤트는 재개 시점을 판단하는 제 3의 스레드가 재개를 위한 시그널을 set해주어야 한다. 배리어는 그와 달리 정원을 채우면 출발하는 버스처럼, 미리 정해진 개수만큼의 스레드가 배리어 아래에 모이면 자동으로 해제되어, 함께 시작하게 해준다.

배리어를 생성할 때에는 기본적으로 정원을 지정한다. 그외에 타임아웃을 지정해줄 수도 있다. 스레드마다 생성되어 있는 배리어에 대해서 .wait()를 호출하면 해당 스레드가 블럭되고, 배리어의 내부 카운트는 1씩 올라간다. 내부 카운트가 지정된 정원과 같아지면 카운트가 리셋되고 배리어에 호출된 모든 wait() 메소드가 리턴되는 방식이다.

이런 구조 덕분에 락이나 컨디션, 이벤트와 같이 with 구문에서 사용되지 않는다. 다음 예제는 인터넷에서 이미지를 다운로드 하는 예인데, 배리어를 사용해서 동시에 끝나도록 종료 시점을 동기화하는 것을 보여준다.

from threading import Thread, Barrier
from urllib.request import urlopen
NO_THREADS = 4
bar = Barrier(NO_THREADS+1)
def load(url: str):
    res = urlopen(url)
    if res.code != 200:
        bar.wait()
        return
    data = res.read()
    fname = url.rsplit('/', 1)[1]
    with open(fname, 'wb') as f:
        f.write(data)
    bar.wait()
def main():
    urls = ( ... # 웹 상의 이미지 경로 4 개 ... )
    ts = [Thread(target=load, args=(u,)) for u in urls]
    for t in ts:
        t.start()
    bar.wait()
if __name__ == '__main__':
    main()

참고로 threading이 아닌 multiprocessing을 사용하여 프로세스로 대체하는 경우에는 배리어나 락과 같은 동기화 수단을 전역 공간에 두는 방식으로 코드를 작성하면 안된다. 전역 공간에 선언된 배리어는 각 프로세스마다 서로 다른 객체이므로, 각 프로세스들이 재개할 수 없는 상황에 빠지게 될 것이다.

댓글 남기기