Python의 스레드 잠금
이 자습서에서는 Python에서 스레드 잠금을 활용하는 다양한 방법에 대해 설명합니다.
Python의 경쟁 조건
경쟁 조건은 여러 스레드가 동일한 공유 변수를 수정하려고 할 때 발생하는 문제입니다. 모든 스레드는 동시에 공유 변수에서 동일한 값을 읽습니다. 그런 다음 모든 스레드가 공유 변수의 값을 수정하려고 합니다. 그러나 변수는 이전 스레드가 쓴 값을 덮어쓰기 때문에 마지막 스레드의 값만 저장하게 됩니다. 이러한 의미에서 모든 스레드 간에는 어느 스레드가 결국 변수의 값을 수정하는지 보기 위한 경쟁이 있습니다. 이 현상은 다음 코드의 예를 통해 설명됩니다.
from threading import Thread
counter = 0
def increase(by):
global counter
local_counter = counter
local_counter += by
counter = local_counter
print(f"counter={counter}")
t1 = Thread(target=increase, args=(10,))
t2 = Thread(target=increase, args=(20,))
t1.start()
t2.start()
t1.join()
t2.join()
print(f"The final counter is {counter}")
출력:
counter=10
counter=20
The final counter is 20
전역 공유 변수 counter = 0
과 두 개의 스레드 t1
및 t2
가 있습니다. 스레드 t1
은 counter
값을 10만큼 증가시키려고 시도하고 t2
스레드는 counter
값을 20만큼 증가시키려고 시도합니다. 위 코드에서 두 스레드를 동시에 실행하고 counter
의 값을 수정하려고 합니다. 위의 논리에 따르면 counter
의 최종 값은 30이어야 합니다. 그러나 경쟁 조건으로 인해 counter
는 10 또는 20입니다.
Python의 스레드 잠금
스레드 잠금은 경쟁 조건을 방지하는 데 사용됩니다. 스레드 잠금은 한 스레드가 공유 변수에 액세스할 때 다른 스레드가 액세스할 수 없도록 잠금을 설정한 다음 스레드가 공유 변수를 사용하지 않을 때 잠금을 제거하여 다른 스레드에서 해당 변수를 처리할 수 있도록 합니다. 스레딩 모듈 내부의 Lock
클래스는 Python에서 스레드 잠금을 생성하는 데 사용됩니다. acquire()
메서드는 공유 변수에 대한 액세스를 잠그는 데 사용되며 release()
메서드는 잠금을 해제하는 데 사용됩니다. 잠금 해제된 잠금에서 사용되는 경우 release()
메서드는 RuntimeError
예외를 발생시킵니다.
from threading import Thread, Lock
counter = 0
def increase(by, lock):
global counter
lock.acquire()
local_counter = counter
local_counter += by
counter = local_counter
print(f"counter={counter}")
lock.release()
lock = Lock()
t1 = Thread(target=increase, args=(10, lock))
t2 = Thread(target=increase, args=(20, lock))
t1.start()
t2.start()
t1.join()
t2.join()
print(f"The final counter is {counter}")
출력:
counter=10
counter=30
The final counter is 30
글로벌 공유 변수 counter=0
과 두 개의 스레드 t1
및 t2
를 만들었습니다. 두 스레드 모두 동일한 increase()
함수를 대상으로 합니다. increase(by, lock)
함수는 두 개의 매개변수를 사용합니다. 첫 번째 매개변수는 counter
를 증가시키는 양이고 두 번째 매개변수는 Lock
클래스의 인스턴스입니다. 이전 선언 외에도 Python의 threading
모듈 내부에 Lock
클래스의 인스턴스 lock
도 만들었습니다. increase(by, lock)
함수의 이 lock
매개변수는 lock.acquire()
함수로 counter
변수에 대한 액세스를 잠그고 스레드가 수정하는 동안 lock
으로 잠금을 해제합니다. 하나의 스레드가 counter
변수를 수정한 경우 release()
함수. 스레드 t1
은 counter
의 값을 10씩 증가시키고 t2
스레드는 counter
의 값을 20씩 증가시킵니다.
스레드 잠금으로 인해 경쟁 조건이 발생하지 않으며 counter
의 최종 값은 30입니다.
Python에서 with lock:
을 사용한 스레드 잠금
이전 방법의 문제는 하나의 스레드가 처리를 완료했을 때 잠긴 각 변수를 조심스럽게 잠금 해제해야 한다는 것입니다. 올바르게 수행되지 않으면 첫 번째 스레드에서만 공유 변수에 액세스할 수 있으며 다른 스레드에는 공유 변수에 대한 액세스 권한이 부여되지 않습니다. 이 문제는 컨텍스트 관리를 사용하여 피할 수 있습니다. with lock:
을 사용하고 중요한 코드를 모두 이 블록 안에 넣을 수 있습니다. 이것은 경쟁 조건을 방지하는 훨씬 쉬운 방법입니다. 다음 코드 스니펫은 Python에서 경쟁 조건을 방지하기 위해 with lock:
을 사용하는 방법을 보여줍니다.
from threading import Thread, Lock
counter = 0
def increase(by, lock):
global counter
with lock:
local_counter = counter
local_counter += by
counter = local_counter
print(f"counter={counter}")
lock = Lock()
t1 = Thread(target=increase, args=(10, lock))
t2 = Thread(target=increase, args=(20, lock))
t1.start()
t2.start()
t1.join()
t2.join()
print(f"The final counter is {counter}")
출력:
counter=10
counter=30
The final counter is 30
with lock:
블록 안에 counter
를 증가시키는 코드를 배치했습니다. 스레드 t1
은 counter
값을 10씩 증가시키고 t2
스레드는 counter
값을 20씩 증가시킵니다. 경쟁 조건은 발생하지 않으며 counter
의 최종 값은 30입니다. 게다가 , 스레드 잠금 해제에 대해 걱정할 필요가 없습니다.
두 방법 모두 완벽하게 작업을 수행합니다. 즉, 두 방법 모두 경쟁 조건이 발생하는 것을 방지하지만 두 번째 방법은 스레드 잠금의 잠금 및 잠금 해제를 처리하는 두통을 방지하기 때문에 첫 번째 방법보다 훨씬 우수합니다. 또한 첫 번째 방법보다 쓰기가 훨씬 깔끔하고 읽기 쉽습니다.
Maisam is a highly skilled and motivated Data Scientist. He has over 4 years of experience with Python programming language. He loves solving complex problems and sharing his results on the internet.
LinkedIn