쓰레드(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은 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억이라는 값을 얻게되었습니다.