[Python] Thread and Lock (쓰레드와 락)

hukim·2020년 9월 20일
3

Python

목록 보기
8/12

쓰레드(Thread)란?

쓰레드(Thread)는 프로그램의 실행 흐름입니다. 하나의 프로세스 안에서 여러 개의 쓰레드를 만들 수 있습니다. 프로세스란 말은 메모리에 할당되어 있는 한 개의 프로그램을 의미하고, 프로그램 안에서 여러 개의 프로세스를 운영할 수 없기 때문에 프로그램이 하나의 프로세스라는 개념입니다.
한 개의 프로세스에서 여러 개의 쓰레드를 가지는 병렬 처리 방식이라고 생각하면 됩니다.
프로세스가 부여된 자원을 이용해서 같은 프로세스 내에서 여러 쓰레드들끼리 자원을 공유할 수 있습니다.

import time

if __name__ == "__main__":
    
    increased_num = 0

    start_time = time.time()
    for i in range(100000000):
        increased_num += 1

    print("--- %s seconds ---" % (time.time() - start_time))

    print("increased_num=",end=""), print(increased_num)
    print("end of main")

위 프로그램은 하나의 프로세스에서 하나의 쓰레드로 increased_num을 0부터 1억까지 더하는 프로그램입니다.
실행시 약 13.6초 걸렸고 결과는 1억이 출력되었습니다.

그럼 위의 프로그램에 쓰레드를 하나 더 추가해서 각각 쓰레드 하나마다 5천만번씩 증가시키게끔 해보도록 하겠습니다.

import threading
import time

shared_number = 0

def thread_1(number):
    global shared_number
    print("number = ",end=""), print(number)
    
    for i in range(number):
        shared_number += 1

def thread_2(number):
    global shared_number
    print("number = ",end=""), print(number)
    
    for i in range(number):
        shared_number += 1


if __name__ == "__main__":

    threads = [ ]

    start_time = time.time()
    t1 = threading.Thread( target= thread_1, args=(50000000,) )
    t1.start()
    threads.append(t1)

    t2 = threading.Thread( target= thread_2, args=(50000000,) )
    t2.start()
    threads.append(t2)


    for t in threads:
        t.join()

    print("--- %s seconds ---" % (time.time() - start_time))

    print("shared_number=",end=""), print(shared_number)
    print("end of main")

쓰레드를 하나 더 추가해서 각각 5천만번씩 숫자를 증가시켰지만 shared_number의 값은 기대했던 1억이 아니라 이상한 숫자가 나옵니다.

그 이유는 같은 변수를 동시에 접근했기 때문입니다.

전역 변수 shared_number가 0으로 초기화 되어있고, thread_1과 thread_2가 동시에
shared_number = shared_number + 1 를 실행한다고 가정해보겠습니다.

cpu가 각각 thread에서 증가하는 과정을 번갈아가면서 실행한다고 했을 때,

  • thread_1 에서 shared_number(0) + 1 로 레지스터 값이 1로 바뀌엇고 이를 shared_number에 저장해서 shared_number가 1로 값이 변경되었습니다.
  • 그 다음 thread_2에서 같은 과정을 진행한다고 하면 똑같이 sheard_number(0) + 1로
    레지스터 값이 1로 바뀌고 다시 shared_number에 저장하니까
  • 최종적으로는 shared_number가 1로 유지가 된것입니다.

thread_1과 thread_2에서 똑같이 shared_number = shared_number + 1을 실행했지만
최종 shared_number의 값은 2가 아닌 1이 된것입니다.

이를 해결하기 'Lock'을 이용해서 쓰레드를 동기화합니다.

Lock()

Lock은 python threading패키지에서 지원합니다.

lock을 acquire하면 해당 쓰레드만 공유 데이터에 접근할 수 있고, lock을 release 해야만
다른 쓰레드에서 공유 데이터에 접근할 수 있습니다.

import threading
import time

shared_number = 0
lock = threading.Lock() # threading에서 Lock 함수 가져오기

def thread_1(number):
    global shared_number
    print("number = ",end=""), print(number)
    
    lock.acquire() # 작업이 끝나기 전까지 다른 쓰레드가 공유데이터 접근을 금지
    for i in range(number):
        shared_number += 1
    lock.release() # lock 해제

def thread_2(number):
    global shared_number
    print("number = ",end=""), print(number)

    lock.acquire() # thread_2 잠금
    for i in range(number):
        shared_number += 1
    lock.release() # thread_2 해제

if __name__ == "__main__":

    threads = [ ]

    start_time = time.time()
    t1 = threading.Thread( target= thread_1, args=(50000000,) )
    t1.start()
    threads.append(t1)

    t2 = threading.Thread( target= thread_2, args=(50000000,) )
    t2.start()
    threads.append(t2)

    for t in threads:
        t.join()

    print("--- %s seconds ---" % (time.time() - start_time))

    print("shared_number=",end=""), print(shared_number)
    print("end of main")

위와 같이 쓰레드를 동기화시켜 주었고 그 결과
공유데이터였던 shared_number는 1억이라는 값을 얻게되었습니다.

0개의 댓글