반응형

스레드 동기화(Thread Synchronization)

여러 스레드가 동시에 실행되면 데이터 불일치 문제가 발생할 수 있습니다. 동기화를 사용하면 이 문제를 방지할 수 있습니다. 동기화는 데이터 불일치 문제를 극복하기 위해 한 번에 하나의 스레드만 실행할 수 있도록 합니다. 동기화는 실행해야 하는 스레드가 둘 이상인 경우 한 번에 단일 스레드가 임계 영역에 액세스할 수 있도록 보장하는 메커니즘입니다.

동기화 프리미티브를 보장하는 동안 교착 상태 및 경쟁 조건과 같은 두 가지 주요 문제가 발생할 수 있습니다.

 

다음 스크립트는 두 스레드가 함께 실행되어 불규칙하게 출력되는 모습을 보여주고 있습니다.

 

from threading import *

import time

 

def func(n):  

  for i in range(2):

    print("Hi:", end='')

    time.sleep(2)

    print(n)

 

t1 = Thread(target = func, args = ("Park", ))

t2 = Thread(target = func, args = ("Kim", ))

t1.start()

t2.start()

 

Output:

Hi:Hi:ParkKim

Hi:

Hi:Kim

Park

 

 

Lock 사용

Lock에는 두 가지 정의된 상태가 있습니다. 하나는 잠금 상태이고 다른 하나는 잠금 해제 상태입니다.

Lock은 초기 생성 시에 잠금 해제 상태로 생성됩니다. 지원되는 두 가지 메서드는 acquire() release()가 있습니다.

Lockacquire()하면 해당 스레드만 공유 데이터에 접근할 수 있고 Lockrelease()해야만 다른 스레드에서 공유 데이터에 접근 할 수 있습니다.

 

acquire()

-  잠금 상태인 경우, 다른 스레드에서 release()호출을 통해서 잠금 해지 상태로 바꿀 때까지 블록 됩니다. 잠금 상태에서 acquire() 메서드를 호출하면 다시 잠금으로 재설정하고 반환합니다.

-  잠금 해지 상태인 경우, 잠금 상태로 변경하고 반환합니다.

release()

-  잠금 상태인 경우, 잠금 해지 상태로 변경합니다.

-  잠금 해지 상태인 경우, RuntimeError가 발생합니다.

 

다음 스크립트는 잠금 해지 상태에서 release()를 호출했을 때 발생하는 오류입니다.

 

from threading import *

 

lock = Lock()

lock.release()

 

Output:

RuntimeError: release unlocked lock

 

다음 스크립트는 acquire()release()를 사용하는 방법을 보여주고 있습니다. 두 스레드가 순차적으로 출력하고 있는 모습을 보여주고 있습니다.

 

from threading import *

import time

 

lock = Lock()

 

def func(n): 

  lock.acquire() 

  for i in range(2):

    print("Hi:", end='')

    print(n)

  lock.release()

 

t1 = Thread(target = func, args = ("Park", ))

t2 = Thread(target = func, args = ("Kim", ))

t1.start()

t2.start()

 

Output:

Hi:Park

Hi:Park

Hi:Kim

Hi:Kim

 

RLock 사용

스레드가 재귀적으로 리소스에 액세스하면 스레드가 동일한 잠금을 다시 획득하여 스레드가 차단될 수 있습니다. 따라서 Lock 잠금 방법은 재귀 함수 실행에 적합하지 않습니다. 이러한 문제를 처리하기 위해 재진입 잠금(RLock)이 선호됩니다.

재진입 잠금은 동일한 스레드가 여러 번 획득할 수 있는 동기화 프리미티브입니다.

기본 잠금에서 사용되는 잠금 또는 잠금 해제 상태 외에 스레드 및 재귀 수준을 소유한다는 개념을 따릅니다.

잠금 상태의 경우 일부 스레드가 잠금을 소유하는 반면 잠금 해제 상태의 경우 잠금을 소유하는 스레드가 없습니다.

스레드는 잠금을 수행하기 위해 acquire() 메서드를 호출하고 반환합니다.

스레드는 잠금을 해제하기 위해 release() 메서드를 호출합니다.

 

from threading import *

 

lock = RLock()

 

def fact(n):

    lock.acquire() 

    if n == 0:

        opt = 1

    else:

        opt = n * fact(n-1)

    lock.release()

    return opt

 

def opt(n):

    print("Factorial", n, ":", fact(n))

 

t1 = Thread(target = opt, args = (3, ))

t2 = Thread(target = opt, args = (5, ))

t1.start()

t2.start()

 

Output:

Factorial 3 : 6

Factorial 5 : 120

 

다음 표는 LockRlock의 차이를 보여줍니다.

 

Lock Rlock
- 메인 스레드를 포함하여 한 번에 하나의 스레드만 획득할 수 있습니다.
 
- 재귀 및 중첩 함수에서는 선호되지 않습니다.
 
- 객체의 잠금 및 잠금 해제만 담당합니다. 메인 쓰레드와 재귀 수준을 고려하지 않습니다.
- 한 번에 하나의 스레드만 획득할 수 있지만 메인 스레드는 여러 번 획득할 수 있습니다.
 
- 재귀 및 중첩 함수에 가장 적합합니다.
 
 
- 잠금, 잠금 해제, 메인 스레드 및 재귀 수준도 담당합니다.
 

 

세마포어 사용

세마포어는 공유 리소스의 액세스를 제한하는 데 선호됩니다.

세마포어는 모든 acquire() 호출에 의해 감소되고 모든 release() 호출에 의해 증가되는 내부 카운터를 처리합니다. 카운터는 0보다 작을 수 없습니다. 카운터는 동시에 액세스할 수 있는 최대 스레드 수를 나타냅니다. 기본값은 1입니다.

 

기본 구문은 다음과 같습니다.

(세마포어 객체 생성) = Semaphore(counter)

 

사용 예)

s = Semaphore()

여기에서 카운터 값은 1이고 한 번에 하나의 스레드에만 액세스가 허용됩니다.

s = Semaphore(3)

여기서 세마포어 객체는 세 개의 스레드에서 동시에 액세스할 수 있습니다. 나머지 스레드는 세마포어가 해제될 때까지 기다려야 합니다.

 

다음 스크립트는 세마포어의 카운터를 1로 설정하고 수행한 예제입니다. 카운터가 1이므로 한번에 하나의 스레드에만 액세스 할 수 있어서 순차적으로 출력됨을 보여주고 있습니다.

 

from threading import *

import time

 

s = Semaphore(1)

 

def func(n): 

    s.acquire() 

    for i in range(2):

        print("Hi:", end='')

        time.sleep(2)

        print(n)

    s.release()

 

t1 = Thread(target = func, args = ("Park", ))

t2 = Thread(target = func, args = ("Kim", ))

t1.start()

t2.start()

 

Output:

Hi:Park

Hi:Park

Hi:Kim

Hi:Kim

 

다음 스크립트는 세마포어의 카운터를 2로 설정하고 수행한 예제입니다. 두개의 스레드에 동시에 접근하므로 불규칙하게 출력됩니다.

 

from threading import *

import time

 

s = Semaphore(2)

 

def func(n): 

    s.acquire() 

    for i in range(2):

        print("Hi:", end='')

        time.sleep(2)

        print(n)

    s.release()

 

t1 = Thread(target = func, args = ("Park", ))

t2 = Thread(target = func, args = ("Kim", ))

t1.start()

t2.start()

 

Output:

Hi:Hi:KimPark

Hi:

Hi:Park

Kim

반응형

+ Recent posts