[ 스레드 기본 ]
* 파이썬에서 제공되는 threading 모듈을 사용하여 스레드를 구현해 보자
* 각기 다른 기능을 하는 함수 2개를 만들고 스레드로 동시에 실행하는 코드를 작성한다.
- function_1 : 숫자 0부터 9까지 순차적으로 출력하는 함수
- function_2 : 문자 A부터 J까지 순차적으로 출력하는 함수
import threading
# 0 부터 9 까지 순차적으로 출력하는 함
def function_1():
for i in range(10):
print(i)
# A 부터 J 까지 순차적으로 출력하는 함수
def function_2():
for letter in 'ABCDEFGHIJ':
print(letter)
# 실행할 함수를 thread1 과 thread2로 각각 설정
thread1 = threading.Thread(target=function_1)
thread2 = threading.Thread(target=function_2)
# 스레드를 시작한다.
thread1.start()
thread2.start()
# 스레드가 끝날 때까지 기다린다.
thread1.join()
thread2.join()
* 위 코드를 test_threading_01.py로 저장한 후 실행하면 아래와 같다.
* 스레드 1과 스레드 2가 동시에 실행되기 때문에 숫자와 문자가 출력되는 순서는 실행할 때마다 다르게 나타난다.
[ 스레드에서 글로벌 변수 사용 ]
* 각각의 스레드에서 사용되는 변수가 글로벌 변수이면 어떻게 되는지 살펴보자
* 동일한 기능을 하는 함수 2개를 만들고 스레드로 동시에 실행하는 코드를 작성한다.
- function_1 : 글로별 변수 count에 0부터 1000000까지 순차적으로 더하는 함수
- function_2 : 글로별 변수 count에 0부터 1000000까지 순차적으로 더하는 함수
import threading
# 전역 변수를 선언하고 0으로 Set
count = 0
# 0 부터 1000000 까지 1씩 증가시키는 함수1
def function_1():
global count
for _ in range(1000000):
count += 1
# 0 부터 1000000 까지 1씩 증가시키는 함수2
def function_2():
global count
for _ in range(1000000):
count += 1
# 실행할 함수를 thread1 과 thread2로 각각 설정
thread1 = threading.Thread(target=function_1)
thread2 = threading.Thread(target=function_2)
# 스레드를 시작한다.
thread1.start()
thread2.start()
# 스레드가 끝날 때까지 기다린다.
thread1.join()
thread2.join()
# 연산된 count 값을 출력한다.
print(f"Count = {count}")
* 위 코드를 test_threading_02.py로 저장한 후 실행하면 아래와 같다.
[ 하나의 스레드가 자원을 독점하는 방법 ]
* GIL(Gloabl Interpreter Lock)은 하나의 스레드가 자원을 독점하는 형태로 실행된다.
* 멀티스레드일 경우, 여러개의 스레드가 병렬적으로 일하는 것이 아니고 여러개의 스레드 중 하나의 스레드만 실행될 수 있도록 하는 상호 배제 잠금(mutex)을 한다.
* 하나의 스레드가 모든 자원을 가지면 다른 스레드에게 lock을 걸어 다른 스레드는 작업을 못하게 할 수 있다.
* 위의 소스 코드에서 글로벌 변수 값을 2개의 스레드가 동시에 증가시킬 때 결과가 2000000으로 나오긴 했지만 복잡한 연산을 하거나 처리 과정이 복잡할 경우 결과가 엉뚱하게 나올 수 있다.
* 그 이유는 스레드 세이프(thread-safe) 때문인데 하나의 쓰레드가 count값을 변경하는 과정에 다른 쓰레드가 count값을 변경할 수 있기 때문이다.
* 이렇듯 여러 쓰레드가 하나의 공유 자원이 동시에 접근하면서 발생하는 문제를 race condition(경쟁 상태)이라 하며 이 문제를 해결하기 위해 mutex 등을 도입한다.
* Mutex는 이렇게 공유 자원에 하나의 쓰레드만 진입하며 작업을 처리할 수 있도록 만들어진 lock 개념이다.
* 위 소스 코드에서 count 값을 변경하기 직전에 lock.acquire()을 호출해 주고 변경작업이 완료되면 lock.release()을 호출해주면 된다.
import threading
# 스레드 Lock 기능을 설정
lock = threading.Lock()
# 전역 변수를 선언하고 0으로 Set
count = 0
# 0 부터 1000000 까지 1씩 증가시키는 함수1
def function_1():
lock.acquire() # 스레드 Lock을 활성화 시킨다.
global count
for _ in range(1000000):
count += 1
lock.release() # 스레드 Lock을 해제 시킨다.
# 0 부터 1000000 까지 1씩 증가시키는 함수2
def function_2():
lock.acquire()
global count
for _ in range(1000000):
count += 1
lock.release()
# 실행할 함수를 thread1 과 thread2로 각각 설정
thread1 = threading.Thread(target=function_1)
thread2 = threading.Thread(target=function_2)
# 스레드를 시작한다.
thread1.start()
thread2.start()
# 스레드가 끝날 때까지 기다린다.
thread1.join()
thread2.join()
# 연산된 count 값을 출력한다.
print(f"Count in Lock = {count}")
* 위 코드를 test_threading_03.py로 저장한 후 실행하면 아래와 같다.
* 참고로... 현재 내 PC에서는 lock 개념을 추가하지 않아도 count변수가 달라지는 상황은 발생하지 않는다.
* 내 PC가 빨라서 그런지? 아니면 파이썬 threading 라이브러리가 업그레이드 되어서 전역변수를 사용하게 되면 알아서 lock 기능이 적용되는지 정확히 알 수 없다.
* 일단, 2개 이상의 스레드가 동시에 실행될 때 필요 시 lock 개념을 도입한다는 개념만 알고 넘어가자.
'파이선 (python)' 카테고리의 다른 글
[Python] 함수/변수 및 클래스 네이밍 표기법 (1) | 2024.01.03 |
---|---|
[Python] 비동기(Async) 방식 프로그램을 구현해보자 (2) | 2023.10.03 |
[Python] 웹크롤링 시 불필요한 콘솔 메시지 제거하기 (0) | 2023.08.28 |
[Python] Twilio SMS 유료 결제하기 (0) | 2023.07.30 |
[Python] Chrome Driver 없이 자동실행 (0) | 2023.07.27 |