✅ 프로세스
- 한 프로세스는 매번 하나의 로그인만 처리할 수 있기 때문에 동시에 처리할 수가 없다.
- 프로세스는 각자가 고유한 메모리 영역을 가지기 때문에 스레드에 비하면 메모리 사용이 늘어난다는 단점이 있지만, 이 방식을 통해 싱글 머신 아키텍처로부터 여러 머신을 사용하는 분산 어플리케이션으로 쉽게 전환 가능
- 프로세스의 예시는 구글크롬의 탭을 생각하면 된다.
- 한 탭에 문제가 생겨도 다른 탭에 영향이 없다.
✅ 스레드
- 한 프로세스 내에서 여러개의 스레드로 작업을 처리하는 것(멀티스레드)
ex) pycharm에서 추천코드도 보여주고 실행하는 동시에 디버깅하면서 수정하는 것- 스레드의 예시로는 윈도우 탭을 생각해보면 된다.
- 한 탭에 문제가 생기면 모든 탭에 영향을 미쳐 전체가 꺼지게 된다.
컴퓨터에서 동작하고 있는 프로그램을 프로세스라고 한다. 보통 1개의 프로세스는 한 가지 일만 하지만 스레드를 사용하면 한 프로세스 안에서 2가지 또는 그 이상의 일을 동시에 수행할 수 있다.
파이썬은 기본적으로 하나의 스레드에서 실행되어, 하나의 메인 스레드에서 파이썬 코드를 순차적으로 실행한다. 코드를 병렬로 실행하기 위해서는 별도의 스레드를 생성하여야 하는데, threading 모듈을 사용하면 된다.
파이썬은 GILGlobal Interpreter Lock 때문에 특정 시점에 하나의 코드만을 실행하게 된다. 다중 CPU에서 파이썬 코드를 병렬로 실행하기 위해서는 다중 프로세스를 이용하는 multiprocessing 모듈을 사용한다.
- 파이썬에서 스레드를 실행하기 위해서는, threading 모듈의 threading.Thread() 함수를 호출하여 Thread 객체를 얻은 후 Thread 객체의 start() 메서드를 호출하면 된다.
Thread(name=, target=, args=, kwargs=, *, daemon=)
스레드 객체를 생성했다 하더라도 해당 스레드가 바로 시작되지는 않는다. start()를 호출하면 스레드가 그때부터 시작된다.
timeout= 인자를 주어 특정 시간까지만 기다리게 할 수 있다.
타임아웃을 초과해도 예외를 일으키지 않고 None을 리턴하므로 이 경우 is_alive()를 호출하여 스레드가 실행 중인지를 파악할 필요가 있다.
is_alive()
해당 스레드가 동작 중인지 확인한다.
name
스레드의 이름
ident
스레드 식별자. 정수값
native_id
스레드 고유 식별자. ident는 종료된 스레드 이후에 새로 만들어진 다른 스레드에 재활용될 수 있다.
daemon
데몬 스레드 여부 (default = False)
데몬 스레드: 백그라운드에서 실행되는 스레드, 메인스레드가 종료되면 즉시 종료되는 스레드
만약 데몬 스레드가 아니면 해당 서브스레드는 메인스레드가 종료될지라도 자신의 작업이 끝날 때까지 계속 실행된다.
default가 False이기 때문에 별도로 지정하지 않으면 메인스레드가 종료되어도 서브스레드는 끝까지 실행된다.
아래의 예제를 보면 executeThread함수를 target으로 지정하였고, args에 튜플로 파라미터를 지정해주었다.
주의할 점은 target = executeThread() 처럼 지정하면 이는 executeThread() 함수를 실행하여 리턴한 결과를 target에 지정하는 것이므로 잘못된 결과가 나올 수 있다.
import threading
import urllib.request
import time
def executeThread(i):
imageName = f"temp/image-{str(i)}.jpg"
urllib.request.urlretrieve("https://test.com/test.jpg", imageName)
def main():
t0 = time.time()
threads = []
for i in range(10):
thread = threading.Thread(target=executeThread, args=(i,))
threads.append(thread)
thread.start()
for i in threads:
print("i ::: ", i)
i.join()
t1 = time.time()
totalTime = t1- t0
print("Total Execution Time {}".format(totalTime))
if __name__ == "__main__":
main()
Thread 클래스를 파생하여 쓰레드가 실행할 run() 메서드를 재정의해서 사용하는 방식.
Thread 클래스에서 run() 메서드는 스레드가 실제 실행하는 메서드이며, start() 메서드는 내부적으로 이 run() 메서드를 호출한다.
import 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()
==============================================
threading.Thread 로부터 파생클래스를 만드는 방식은 Thread 클래스를 파생하여 쓰레드가 실행할 run() 메서드를 재정의해서 사용하는 방식이다. Thread 클래스에서 run() 메서드는 쓰레드가 실제 실행하는 메서드이며, start() 메서드는 내부적으로 이 run() 메서드를 호출한다. 예를 들어, 아래 예제(A)는 getHtml() 라는 함수를 사용한 방식인데 이를 예제(B)와 같이 파생클래스를 사용하는 방식으로 바꿔 쓸 수 있다. 예제(B)에서 t.start()는 HtmlGetter 클래스에서 재정의된 run() 메서드를 호출하게 된다.
# 예제(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 ###")
# 예제(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 ###")
- join 메서드
사용된 스레드가 끝날 때까지 메인 스레드가 먼저 종료되지 않고 기다렸다가 종료되게 하는 메서드
Thread.start() 는 즉시 리턴하기 때문에 워커 스레드들이 동작하고 있는 중일 때 메인스레드가 적절히 기다려주지 않는다면 프로그램이 중간에 끝날 수 있기 때문에 주의해야 한다. 프로세스의 종료 시점은 메인 스레드가 종료 지점에 도달했을 때이며, 다른 워커 스레드의 실행 여부는 고려되지 않는다. 따라서 중도 종료를 막기 위해서는 join() 메소드를 사용하여 메인 스레드가 워커 스레드들을 기다리도록 하여야 한다.
스레드를 너무 많이 생성하면 성능을 향상시키는데 역효과가 날 수 있다. 심지어 GIL이 없는 구현체에서도 그럴 수 있는데 그것은 스레드가 많으면 컨텍스트 스위치에 많은 비용이 들기 때문이다.
데몬 : 사용자가 직접적으로 제어하지 않고, 백그라운드에서 돌면서 여러 작업을 하는 프로그램
쓰레드를 데몬으로 만들어서 사용할 수도 있다.
주요 작업을 하는 메인 쓰레드가 따로 있고, 백그라운드 작업을 위한 쓰레드를 띄울 수 있다는 것이다. 이러한 백그라운드 작업을 데몬으로 띄우지 않으면, 백그라운드 작업을 하는 쓰레드가 종료될때까지 메인 프로그램도 종료되지 않는다. 프로그램의 상태를 모니터링 하는 프로그램이 내부에서 주기적으로 동작하는데, 만약 이 프로그램 때문에 메인 프로그램이 종료되지 못하면 이상하다. 데몬쓰레드는 메인 프로그램이 종료될때 자동으로 같이 종료된다.
즉, 정기적이고 부수적인 작업은 데몬으로, 필수적인 메인 작업은 일반 쓰레드로 띄운다.
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.daemon = True
t1.start()
print("### End ###")