프로그램의 실행 흐름
쓰레드 작성 예시
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")
위의 프로그램은 단일 프로세스의 단일 쓰레드로 일억번 +1을 하는 프로그램
실행 성능 : 6~7초
두개의 쓰레드 예시
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")
Thread의 장점
- CPU 사용률 향상
- 효율적인 자원 활용 및 응답성 향상
- 코드 간결 및 유지보수성 향상
threading 모듈 이용. import threading
threading에서 내장 모듈인 Thread를 상속 받음
threading.Thread 클래스를 상속받는 클래스를 만들어서 run() gkdu rorcp todtjd
Thread를 구동하기 위해서는 함수명을 run으로 해야함
MyThread로 생성된 객체를 start() 메소드를 실행할 때 run 메소드가 자동으로 수행
thread 객체 생성 후 start() 메소드를 통해 thread 객체 실행
target은 스레드로 돌릴 함수, args는 입력 인자.
join() 메소드를 통해 스레드가 끝날 때까지 기다림
예제 코드
import threading
## threading 모듈을 이용
## threading.Thread를 상속받는 클래스를 만들어서 run하여 객체를 생성한다.
class Messenger(threading.Thread):
def run(self):
for _ in range(10):
## 변수를 지정하지 않고 단순 반복을 하고 싶을 때는 _언더바를 사용한다.
print(threading.currentThread().getName())
send = Messenger(name="Sending out Messages")
receive = Messenger(name="Receiving Messages")
send.start()
receive.start()
출력 결과
from threading import Thread
import time
def my_thread(val):
for i in range(3):
print("I'm Thread")
time.sleep(2)
## 인스턴스 만들기
## 첫번째 argu는 스레드 함수 이름, 두번째 argu는 매개변수를 튜플 형태로 전달한 것.
t1 = Thread(target = my_thread, args=(1,))
## 스레드 시작.
t1.start()
## main
for i in range(3):
print("I'm main Thread")
time.sleep(1)
print("----the end----")
실행 결과
join() 메소드 추가하여 실행
from threading import Thread
import time
def my_thread(val):
for i in range(3):
print("I'm Thread")
time.sleep(1)
## 인스턴스 만들기
## 첫번째 argu는 스레드 함수 이름, 두번째 argu는 매개변수를 튜플 형태로 전달한 것.
t1 = Thread(target = my_thread, args=(1,))
## 스레드 시작.
t1.start()
t1.join()
## main
for i in range(3):
print("I'm main Thread")
time.sleep(1)
print("----the end----")
출력 결과
join() 메소드를 main함수전에 써주면 자식 스레드가 다 출력된 후 메인이 출력되는 것을 확인 할 수 있다.
Thread 사용 시 주의 할 점
전역 변수를 공유한다
문제 예시
import threading
# CounterThread
class CounterThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self, name='Timer Thread')
# CounterThread가 실행하는 함수
def run(self):
global totalCount
# 2,500,000번 카운트 시작
for _ in range(2500000):
totalCount += 1
print('2,500,000번 카운팅 끝!')
if __name__ == "__main__":
# 전역 변수로 totalCount를 선언
global totalCount
totalCount = 0
# totalCount를 1씩 더하는
# Counter Thread를 4개 만들어서 동작시킨다.
for _ in range(4):
timerThread = CounterThread()
timerThread.start()
print('모든 Thread들이 종료될 때까지 기다린다.')
mainThread = threading.currentThread()
for thread in threading.enumerate():
# Main Thread를 제외한 모든 Thread들이
# 카운팅을 완료하고 끝날 때 까지 기다린다.
if thread is not mainThread:
thread.join()
print('totalCount = ' + str(totalCount))
실행 결과
위의 실행 결과 totalCount 변수안에 10,000,000이 아니라 4,827,031이 들어가 있다는 것을 확인
문제의 발생 = 같은 변수를 동시에 접근 했기 때문
ex)
a = a + 1은 보통 3가지의 명령어로 진행 그리고 cpu는 명령어를 하나씩 실행
a의 진짜 메모리에 1이 더해지려면 3번째 명령어까지 실행 되어야 한다.
하지만 이 3가지 명령어을 4개의 thread가 거의 동시에 실행하려다 보면 한 thread에서 3번째 명령어가 다 끝나기 전에 또 다른 thread에서 덧셈을 시작하게 된다.
이러한 현상 때문에 여러 thread에서 a에 1씩 더했다 하더라도 최종적으로 더해진 변수의 값을 살펴보면 더 작아져서 나오게 된다.
전역 변수 a가 0으로 초기화되어 있을 때, thread A와 thread B가 서로 동시에 a=a+1을 실행하려고 한다
이 때 CPU는 동시에 실행되는 것처럼 보이게 하려고 각각의 Thread의 명령어을 번갈아 가면서 실행
Thread를 동기화 한다
동기화 하는 방법 중에 lock을 이용
Lock => 특정 thread에서 변수를 사용하기 때문에 다른 thread가 사용하지 못 하도록 막는 역할
Lock 사용법
Lock 사용 예시 코드
import threading
# 공유된 변수를 위한 클래스
class ThreadVariable():
def __init__(self):
self.lock = threading.Lock()
self.lockedValue = 0
# 한 Thread만 접근할 수 있도록 설정한다
def plus(self, value):
# Lock해서 다른 Thread는 기다리게 만든다.
self.lock.acquire()
try:
self.lockedValue += value
finally:
# Lock을 해제해서 다른 Thread도 사용할 수 있도록 만든다.
self.lock.release()
# CounterThread
class CounterThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self, name='Timer Thread')
# CounterThread가 실행하는 함수
def run(self):
global totalCount
# 2,500,000번 카운트 시작
for _ in range(2500000):
totalCount.plus(1)
print('2,500,000번 카운팅 끝!')
if __name__ == '__main__':
# 전역 변수로 totalCount를 선언
global totalCount
# totalCount를 ThreadVariable 오브젝트로 초기화한다
totalCount = ThreadVariable()
# totalCount를 1씩 더하는
# Counter Thread를 4개 만들어서 동작시킨다.
for _ in range(4):
timerThread = CounterThread()
timerThread.start()
print('모든 Thread들이 종료될 때까지 기다린다.')
mainThread = threading.currentThread()
for thread in threading.enumerate():
# Main Thread를 제외한 모든 Thread들이
# 카운팅을 완료하고 끝날 때 까지 기다린다.
if thread is not mainThread:
thread.join()
print('totalCount = ' + str(totalCount.lockedValue))
실행 결과