스레드란?
스레드(Thread)는 프로그램이 동작할 때, 동시에 여러 작업을 수행할 수 있게 해주는 중요한 개념입니다. 이는 파이썬뿐만 아니라 모든 프로그래밍 언어에서 중요한 개념으로 사용됩니다.
프로세스와 스레드
프로세스는 운영체제 상에서 실행 중인 하나의 프로그램을 나타냅니다. 각 프로세스는 독립적으로 실행되며, 각각이 별도의 메모리 공간을 가집니다. 이와 달리, 스레드는 프로세스 내에서 동작하며, 같은 프로세스 내의 스레드들은 메모리를 공유합니다. 이를 통해 작업을 빠르게 처리할 수 있습니다.
import threading
def function1():
for i in range(50000 + 1):
print("function1: ", i)
def function2():
for i in range(50000 + 1):
print("function2: ", i)
th1 = threading.Thread(target=function1)
th2 = threading.Thread(target=function2)
th1.start()
th2.start()
th1.join()
th2.join()
위의 예시에서는 function1과 function2 함수를 동시에 실행하기 위해 두 개의 스레드를 생성합니다. 각 스레드는 target 매개변수에 스레드로 동작할 함수를 지정하며, start() 함수로 실행됩니다. 마지막으로 join() 함수를 사용하여 스레드가 종료될 때까지 기다립니다.
파이썬 GIL (Global Interpreter Lock)
파이썬은 GIL(Global Interpreter Lock)이라는 특징을 가지고 있습니다.
이는 파이썬이 한 번에 하나의 명령만을 처리하도록 하는 것입니다. 따라서 한 번에 하나의 스레드만이 실행됩니다.
GIL(Global Interpreter Lock)은 인터프리터 언어로 동작하는 파이썬에서, 한 번에 하나의 스레드만이 파이썬 객체에 접근할 수 있도록 하는 기능이기에 여러 스레드가 동시에 실행될 수 있는 C나 C++과 같은 언어와는 다르게 작동합니다.
GIL은 파이썬이 안전하게 실행될 수 있도록 해주지만, 동시에 여러 스레드가 병렬적으로 실행되는 것을 제한합니다.
따라서 CPU 바운드 작업(계산 위주의 작업)을 병렬로 처리하는 데는 한계가 있지만, I/O 바운드 작업(파일 읽기/쓰기, 네트워크 통신 등)의 경우에는 여러 스레드가 동시에 실행될 수 있어 성능 향상을 가져올 수 있습니다.
이러한 이유로, 파이썬은 I/O 바운드 작업에 매우 효과적이이나, CPU 바운드 작업에는 병렬 처리의 이점을 제공하지 못할 수 있습니다.
스레드의 활용
스레드를 사용하면 작업을 단위로 나누어 동시에 실행할 수 있습니다.
특히 작업 중 일부가 지연될 경우, 각 스레드가 별도의 작업을 처리하므로 전체적인 성능이 향상될 수 있습니다.
def function():
a = []
for i in range(1000):
a.append(random.random())
time.sleep(0.001)
print(f"min: {min(a):.2f}, max: {max(a):.2f}")
위의 예시에서는 function() 함수에서 각 반복마다 0.001초의 딜레이를 추가하였습니다.
이렇게 작업 중에 지연이 발생하는 경우, 스레드를 사용하여 작업을 병렬로 처리할 때 높은 효율을 얻을 수 있습니다.
import threading
bread_plate = 0 # 빵 접시 : 공유 자원
lock = threading.Condition() # Lock을 위한 조건변수
class Maker(threading.Thread):
def run(self):
global bread_plate
for i in range(30):
lock.acquire()
while bread_plate >= 10:
print('빵 생산 초과로 대기')
lock.wait() # 스레드의 비활성화
bread_plate += 1
print('빵 생산 수 : ', bread_plate)
lock.notify() # 스레드의 활성화
lock.release()
class Consumer(threading.Thread):
def run(self):
global bread_plate
for i in range(30):
lock.acquire()
while bread_plate < 1:
print('빵이 없어 기다림')
lock.wait() # 스레드의 비활성화
bread_plate -= 1
print('빵 소비 후 남은 수 : ', bread_plate)
lock.notify() # 스레드의 활성화
lock.release()
mak = []
con = []
for i in range(5):
mak.append(Maker())
for i in range(5):
con.append(Consumer())
for th1 in mak:
th1.start()
for th2 in con:
th2.start()
for th1 in mak:
th1.join()
for th2 in con:
th2.join()
print('프로그램 종료')
빵 생산자(Maker)와 빵 소비자(Consumer)를 나타내는
두 개의 스레드를 사용하는 예제