여러 스레드가 동시에 공유 자원에 접근하면 데이터 충돌이나 예측 불가능한 결과가 발생할 수 있습니다. Python의 threading 모듈은 이러한 문제를 방지하기 위해 간단하게 구현할 수 있는 락(Lock) 메커니즘을 제공하며, 이를 통해 여러 스레드의 실행을 동기화할 수 있습니다.
Lock 객체 생성하기
새로운 락은 Lock() 메서드를 호출하여 생성합니다. 이 메서드는 새로운 락 객체를 반환하며, 이후 스레드 동기화에 사용됩니다.
import threading threadLock = threading.Lock()
acquire() 메서드로 락 획득하기
생성된 락 객체의 acquire(blocking) 메서드는 스레드가 순차적으로 실행되도록 강제하는 역할을 합니다. 선택적 매개변수인 blocking을 사용하면 락을 기다릴지 여부를 제어할 수 있습니다.
- blocking = 0: 락을 즉시 획득하지 못하면 대기하지 않고 바로
0(False)을 반환하고, 락을 획득하면1(True)을 반환합니다. - blocking = 1: 락이 해제될 때까지 스레드가 차단(blocking)된 상태로 대기합니다.
release() 메서드로 락 해제하기
락 객체의 release() 메서드는 더 이상 락이 필요하지 않을 때 호출하여 락을 해제합니다. 락을 해제하면 대기 중인 다른 스레드가 해당 락을 획득할 수 있게 됩니다.
실전 예제
다음 예제는 두 개의 스레드가 하나의 락을 공유하여 시간 출력 작업을 순차적으로 수행하는 코드입니다.
#!/usr/bin/python3
import threading
import time
class myThread(threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print("Starting " + self.name)
# 스레드 동기화를 위해 락 획득
threadLock.acquire()
print_time(self.name, self.counter, 3)
# 다음 스레드를 위해 락 해제
threadLock.release()
def print_time(threadName, delay, counter):
while counter:
time.sleep(delay)
print("%s: %s" % (threadName, time.ctime(time.time())))
counter -= 1
threadLock = threading.Lock()
threads = []
# 새로운 스레드 생성
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 스레드 시작
thread1.start()
thread2.start()
# 스레드를 리스트에 추가
threads.append(thread1)
threads.append(thread2)
# 모든 스레드가 종료될 때까지 대기
for t in threads:
t.join()
print("Exiting Main Thread")실행 결과
위 코드를 실행하면 다음과 같은 결과가 출력됩니다. Thread-1이 먼저 락을 획득해 작업을 마친 후, Thread-2가 순차적으로 실행되는 것을 확인할 수 있습니다.
Starting Thread-1 Starting Thread-2 Thread-1: Thu Mar 21 09:11:28 2013 Thread-1: Thu Mar 21 09:11:29 2013 Thread-1: Thu Mar 21 09:11:30 2013 Thread-2: Thu Mar 21 09:11:32 2013 Thread-2: Thu Mar 21 09:11:34 2013 Thread-2: Thu Mar 21 09:11:36 2013 Exiting Main Thread
정리
스레드 동기화는 멀티스레드 프로그래밍에서 안정성을 보장하는 핵심 기술입니다. threading.Lock()으로 락을 생성하고, 임계 영역(critical section) 진입 전에 acquire(), 작업 완료 후에는 반드시 release()를 호출하는 패턴을 익혀두면 공유 자원 관련 버그를 효과적으로 예방할 수 있습니다. 참고로 Python 3에서는 with threadLock: 구문을 사용하면 락의 자동 획득 및 해제가 가능해 더욱 안전한 코드를 작성할 수 있습니다.