목차
- 프로세스 스레드
- 스레드란?
- threading 모듈 사용하기
- 데몬 스레드 만들기
프로세스와 스레드
운영체제에서 어떤 실행 프로그램이 실행된다는 것은 CPU, 메모리, SSD와 같은 컴퓨터 자원을 사용합니다. 따라서 운영체제는 프로그램들이 마음껏 실행될 수 있도록 전용 '놀이터'와 같은 공간을 제공해주는데 이를 프로세스라고 합니다. 응용 프로그램의 코드는 이 놀이터에서 마음껏 놀 수 (실행할 수) 있으며 외부 세계에 대해서 걱정할 필요가 없습니다. 하지만 만약 어떤 코드가 자신에게 부여받은 놀이터 공간을 벗어나 다른 영역으로 가려면 하면 운영체제에 의해 종료되어버립니다.
놀이터에는 응응 프로그램이 놀 수 있습니다. 운영체제 입장에서 놀이터에 있는 플레이어를 스레드라고 부릅니다. 어떤 응용 프로그램은 한 번에 여러 가지 작업을 수행해야 하는 경우도 있습니다. 이 경우 동일한 놀이터(프로세스)에 두 아이(스레드)가 있는데 놀이터에 있는 모든 장난감(컴퓨터 자원)은 공유한다고 생각하면 됩니다.
스레드(Thread)란?
여러분이 사용하는 PC에는 윈도우, macOS, 리눅스와 같은 운영체제가 설치되어 있습니다. 운영체제는 컴퓨터를 전체적으로 관리하는 매니저 역할을 합니다. 우리가 프로그램이라고 부르는 것들은 운영체제 위에서 동작합니다. 프로그램이 메모리에 올라가서 실행 중인 것을 프로세스(process)라고 부릅니다. 프로세스의 실행 단위를 스레드라고 합니다. 프로세스는 최소 하나 이상의 스레드를 갖으며 경우에 따라 여러 스레드를 가질 수도 있습니다.
여러분이 윈도우를 사용할 때를 생각해봅시다. 메신저도 사용하고 게임도 하고 문서 작성도 하고 인터넷도 사용할 것입니다. 윈도우는 동시에 실행되는 여러 프로그램들을 잘 관리해야 하는데 이런 작업을 스케줄링이라고 합니다. 운영체제는 스케쥴의 단위로 앞서 설명한 스레드를 사용합니다. 여러분이 프로그램을 작성할 때 여러 개의 스레드를 사용할 수 있는데 이를 멀티스레드라고 부릅니다. 이처럼 프로그램을 작성할 때 멀티스레드 형태로 구현을 하면 운영체제에 의해서 동시에 스케줄링(동시처럼 느껴지는) 될 수 있기 때문에 보통 성능이 더 좋아집니다.
파이썬 프로그램은 기본적으로 하나의 쓰레드(Single Thread)에서 실행됩니다. 즉, 하나의 메인 쓰레드가 파이썬 코드를 순차적으로 실행합니다. 코드를 병렬로 실행하기 위해서는 별도의 쓰레드(Subthread)를 생성해야 하는데, 파이썬에서 쓰레드를 생성하기 위해서는 threading 모듈 (High 레벨) 혹은 thread 모듈 (Low 레벨)을 사용할 수 있습니다. 일반적으로 쓰레드 처리를 위해서는 thread 모듈 위에서 구현된 threading 모듈을 사용하고 있으며, thread 모듈은 (deprecate 되어) 거의 사용하고 있지 않습니다.
파이썬(오리지날 파이썬 구현인 CPython)은 전역 인터프리터 락킹(Global Interpreter Lock) 때문에 특정 시점에 하나의 파이썬 코드만을 실행하게 되는데, 이 때문에 파이썬은 실제 다중 CPU 환경에서 동시에 여러 파이썬 코드를 병렬로 실행할 수 없으며 인터리빙(Interleaving) 방식으로 코드를 분할하여 실행합니다. 다중 CPU 에서 병렬 실행을 위해서는 다중 프로세스를 이용하는 multiprocessing 모듈을 사용합니다.
threading 모듈 사용하기
파이썬에서 스레드를 다루는 다양한 방법이 있습니다. 파이썬 기본 모듈로는 thread와 threading 모듈이 있는데 보통 theading 모듈을 더 자주 사용합니다. 이외에도 GUI 라이브러리인 PyQt의 QThread를 사용하기도 합니다. 이장에서는 threading 모듈을 사용해서 스레드를 생성해 보겠습니다
import threading
import time
class Worker(threading.Thread):
def __init__(self, name):
super().__init__()
self.name = name # thread 이름 지정
def run(self):
print("sub thread start ", threading.currentThread().getName())
time.sleep(3)
print("sub thread end ", threading.currentThread().getName())
print("main thread start")
for i in range(5):
name = "thread {}".format(i)
t = Worker(name) # sub thread 생성
t.start() # sub thread의 run 메서드를 호출
print("main thread end")
|
cs |
실행 결과는 다음과 같습니다. 메인 스레드가 5개의 서브 스레드를 생성하고 start 메서드를 호출하여 Worker 클래스에 정의한 run( ) 메서드를 호출합니다. 메인 스레드와 5개의 서브 스레드는 운영체제의 스케줄러에 의해 스케줄링 되면서 실행됩니다. 가장 먼저 메인 스레드가 끝나면서 'main thread end'를 출력합니다. 서브 스레드들은 0, 1, 2, 3, 4 순으로 실행됐지만 종료 순서는 조금 다른 것을 확인할 수 있습니다. 기본적으로 메인 스레드에서 서브 스레드를 생성하면 메인 스레드는 자신의 작업을 모두 마쳤더라도 서브 스레드의 작업이 종료될 때 까지 기다렸다가 서브 스레드의 작업이 모두 완료되면 종료됩니다.
main thread start
sub thread start thread 0
sub thread start thread 1
sub thread start thread 2
sub thread start thread 3
sub thread start thread 4
main thread end
sub thread end thread 0
sub thread end thread 1
sub thread end sub thread end thread 2
thread 4
sub thread end thread 3
|
cs |
파이썬에서 쓰레드를 실행하기 위해서는, threading 모듈의 threading.Thread() 함수를 호출하여 Thread 객체를 얻은 후 Thread 객체의 start() 메서드를 호출하면 됩니다. 서브쓰레드는 함수 혹은 메서드를 실행하는데, 일반적인 구현방식으로 (1) 쓰레드가 실행할 함수 혹은 메서드를 작성하거나 또는 (2) threading.Thread 로부터 파생된 파생클래스를 작성하여 사용하는 방식 등이 있습니다.
먼저 첫번째 함수 및 메서드 실행 방식은 쓰레드가 실행할 함수 (혹은 메서드)를 작성하고 그 함수명을 threading.Thread() 함수의 target argument에 지정하면 됩니다. 예를 들어, 아래 예제에서 sum 이라는 함수를 쓰레드가 실행하도록 threading.Thread() 함수의 파라미터로 target=sum 을 지정하였습니다.
여기서 한가지 주의할 점은 target=sum() 처럼 지정하면, 이는 sum() 함수를 실행하여 리턴한 결과를 target에 지정하는 것이므로 잘못된 결과를 초래할 수 있습니다. 만약 쓰레드가 실행하는 함수(혹은 메서드)에 입력 파라미터를 전달해야 한다면, args (혹은 키워드 아규먼트인 경우 kwargs) 에 필요한 파라미터를 지정하면 됩니다.
args는 튜플로 파라미터를 전달하고, kwargs는 dict로 전달합니다.
아래 예제에서 sum() 함수는 두 개의 파라미터를 받아들이기 때문에 "args=(1, 100000)" 와 같이 입력 파라미터를 지정하였습니다.
import threading
def sum(low, high):
total = 0
for i in range(low, high):
total += i
print("Subthread", total)
t = threading.Thread(target=sum, args=(1, 100000))
t.start()
print("Main Thread")
|
cs |
(실행결과)
$ python thrd.py
Main Thread
Subthread 4999950000
(매번 실행 결과는 달라질 수 있습니다.)
threading.Thread 로부터 파생클래스를 만드는 방식은 Thread 클래스를 파생하여 쓰레드가 실행할 run() 메서드를 재정의해서 사용하는 방식입니다. Thread 클래스에서 run() 메서드는 쓰레드가 실제 실행하는 메서드이며, start() 메서드는 내부적으로 이 run() 메서드를 호출합니다.
예를 들어, 아래 예제(A)는 getHtml() 라는 함수를 사용한 방식인데 이를 예제(B)와 같이 파생클래스를 사용하는 방식으로 바꿔 쓸 수 있습니다. 예제(B)에서 t.start()는 HtmlGetter 클래스에서 재정의된 run() 메서드를 호출하게 된다.
(주: requests는 외부 HTTP 라이브러리로서 http://pythonstudy.xyz/python/article/403 아티클을 참고하십시오)
# 예제(A)
import threading, requests, time
def getHtml(url):
resp = requests.get(url)
time.sleep(1)
print(url, len(resp.text), ' chars')
t1 = threading.Thread(target=getHtml, args=('http://google.com',))
t1.start()
print("### End ###")
|
cs |
# 예제(B)
import threading, requests, time
class HtmlGetter (threading.Thread):
def __init__(self, url):
threading.Thread.__init__(self)
self.url = url
def run(self):
resp = requests.get(self.url)
time.sleep(1)
print(self.url, len(resp.text), ' chars')
t = HtmlGetter('http://google.com')
t.start()
print("### End ###")
|
cs |
데몬 스레드 만들기
데몬(daemon) 스레드는 메인 스레드가 종료될 때 자신의 실행 상태와 상관없이 종료되는 서브 스레드를 의미합니다. 앞서 threading 모듈을 사용해서 메인 스레드가 서브 스레드를 생성하는 경우 메인 스레드는 서브 스레드가 모두 종료될 때까지 기다렸다가 종료하게 됩니다. 그런데 실제 프로그래밍을 하다보면 경우에 따라 메인 스레드가 종료되면 모두 서브스레드가 동작 여부에 상관없이 종료되어야 하는 경우가 많습니다. 예를 들어 토렌토와 같은 파일 다운로드 프로그램에서 서브 스레드를 통해 파일을 동시에 다운로드 받고 있는데 사용자가 메인 프로그램을 종료하면 파일의 다운로드 완료 여부와 상관없이 프로그램이 종료되어야 할 것입니다. 이때 서브 스레드 들은 데몬 스레드로 만들어져야 합니다. 파이썬 threading 모듈에서 데몬 스레드의 생성은 daemon 속성을 True로 변경하면 됩니다.
daemon 속성은 디폴트로 False 이므로 별도로 지정하지 않으면 메인 쓰레드가 종료되어도 서브쓰레드는 끝까지 작업을 수행합니다.
import threading
import time
class Worker(threading.Thread):
def __init__(self, name):
super().__init__()
self.name = name # thread 이름 지정
def run(self):
print("sub thread start ", threading.currentThread().getName())
time.sleep(3)
print("sub thread end ", threading.currentThread().getName())
print("main thread start")
for i in range(5):
name = "thread {}".format(i)
t = Worker(name) # sub thread 생성
t.daemon = True
t.start() # sub thread의 run 메서드를 호출
print("main thread end")
|
cs |
실행 결과를 확인해보면 메인 스레드가 종료되면서 time.sleep(3)에 의해 대기 상태에 있던 서브 스레드들은 끝나기 전에 모두 종료된 것을 확인할 수 있습니다.
main thread start
sub thread start thread 0
sub thread start thread 1
sub thread start thread 2
sub thread start thread 3
sub thread start thread 4
main thread end
여기까지 파이썬 스레드에 관한 기본 지식에 대해 공부를 해보았습니다.
하지만 공부를 하다보니 몇가지 의문점이 생겨 이에 대한 내용은 다른 페이지에 정리하도록 하겠습니다.
추가적인 지식도 추후 다루도록 하겠습니다.