본문으로 바로가기

소개

 

둘 이상의 쓰레드가 동일한 데이터를 공유하여 발생하는 문제를 해결하기 위한 동기화 기법에 대해 알아보

겠습니다.

 

  • 세마포어(Semaphore): 공유 자원에 여러 프로세스가 접근하는 것을 막는 것

  • 뮤텍스(Mutex): 공유 자원에 여러 쓰레드가 접근하는 것을 막는 것

 

예제 1

 

두 쓰레드가 동일 변수에 접근하며, 그 과정에서 에러가 발생하도록 유도된 코드입니다.

결과는 90이 아닌 45

import threading
import time
import random

def run(n):
    global total_footprint
    for i in range(10):
        print(f'{n} {i}')
        tmp = total_footprint
        time.sleep(0.1)
        total_footprint = tmp + i
    print(f'* [{n} done]')

if __name__ == '__main__':
    lock = threading.Lock()
    players = ['rabbit', 'turtle']
    total_footprint = 0
    t1 = threading.Thread(target=run, args=(players[0],))
    t2 = threading.Thread(target=run, args=(players[1],))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(f'total_footprint: {total_footprint}')
rabbit 0
turtle 0
turtle 1
rabbit 1
turtle 2
rabbit 2
turtle 3
rabbit 3
turtle 4
rabbit 4
turtle 5
rabbit 5
turtle 6
rabbit 6
turtle 7
rabbit 7
turtle 8
rabbit 8
turtle 9
rabbit 9
* [turtle done]
* [rabbit done]
total_footprint: 45

 

임시 변수에 대입 과정에 딜레이를 주는 것만으로 오류가 발생합니다.

tmp = total_footprint
time.sleep(0.1)
total_footprint = tmp + i

 

 

예제 2

 

동기화를 구현한 코드입니다.

뮤텍스(Lock)를 사용하여 각 쓰레드에 대한 데이터 접근을 통제합니다.

import threading
import time
import random

def run(n):
    global total_footprint
    for i in range(10):
        print(f'{n} {i}')
        lock.acquire()
        tmp = total_footprint
        time.sleep(0.1)
        total_footprint = tmp + i
        lock.release()
    print(f'* [{n} done]')

if __name__ == '__main__':
    lock = threading.Lock()
    players = ['rabbit', 'turtle']
    total_footprint = 0
    t1 = threading.Thread(target=run, args=(players[0],))
    t2 = threading.Thread(target=run, args=(players[1],))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(f'total_footprint: {total_footprint}')
rabbit 0
turtle 0
rabbit 1
turtle 1
rabbit 2
turtle 2
rabbit 3
turtle 3
rabbit 4
turtle 4
rabbit 5
turtle 5
rabbit 6
turtle 6
rabbit 7
turtle 7
rabbit 8
turtle 8
rabbit 9
turtle 9
* [rabbit done]
* [turtle done]
total_footprint: 90

 

사용방법은 데이터를 조작하는 부분 앞 뒤로 acquire() 함수와 release() 함수를 사용하시면 됩니다.

acquire() 가 호출되면 동일 데이터에 접근을 원하는 다른 쓰레드는 대기하며, release() 가 호출되면 다른 쓰레드도 해당 데이터에 선점할 수 있는 기회가 주어집니다.

lock = threading.Lock()

...

lock.acquire()
tmp = total_footprint
time.sleep(0.1)
total_footprint = tmp + i
lock.release()