Python의 스레드 잠금

Muhammad Maisam Abbas 2023년10월8일
  1. Python의 경쟁 조건
  2. Python의 스레드 잠금
  3. Python에서 with lock:을 사용한 스레드 잠금
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과 두 개의 스레드 t1t2가 있습니다. 스레드 t1counter 값을 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과 두 개의 스레드 t1t2를 만들었습니다. 두 스레드 모두 동일한 increase() 함수를 대상으로 합니다. increase(by, lock) 함수는 두 개의 매개변수를 사용합니다. 첫 번째 매개변수는 counter를 증가시키는 양이고 두 번째 매개변수는 Lock 클래스의 인스턴스입니다. 이전 선언 외에도 Python의 threading 모듈 내부에 Lock 클래스의 인스턴스 lock도 만들었습니다. increase(by, lock) 함수의 이 lock 매개변수는 lock.acquire() 함수로 counter 변수에 대한 액세스를 잠그고 스레드가 수정하는 동안 lock으로 잠금을 해제합니다. 하나의 스레드가 counter 변수를 수정한 경우 release() 함수. 스레드 t1counter의 값을 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를 증가시키는 코드를 배치했습니다. 스레드 t1counter 값을 10씩 증가시키고 t2 스레드는 counter 값을 20씩 증가시킵니다. 경쟁 조건은 발생하지 않으며 counter의 최종 값은 30입니다. 게다가 , 스레드 잠금 해제에 대해 걱정할 필요가 없습니다.

두 방법 모두 완벽하게 작업을 수행합니다. 즉, 두 방법 모두 경쟁 조건이 발생하는 것을 방지하지만 두 번째 방법은 스레드 잠금의 잠금 및 잠금 해제를 처리하는 두통을 방지하기 때문에 첫 번째 방법보다 훨씬 우수합니다. 또한 첫 번째 방법보다 쓰기가 훨씬 깔끔하고 읽기 쉽습니다.

Muhammad Maisam Abbas avatar Muhammad Maisam Abbas avatar

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

관련 문장 - Python Thread