Python/Study

python 기초 - Thread (쓰레드) - daemon, join, 객체 등

pybi 2023. 1. 16. 22:45

이번편은 파이썬의 thread이다. thread는 함수 등을 독립적으로 실행시켜 주기 때문에 아주 중요한 개념이고, schedule과 같이 사용하는 등 다양하게 활용이 가능하다. 

 

 

우선 첫번째 방법은 thread를 통해 별도의 함수를 실행시키는 것이다.

import threading

def print_sth():
    print("thread test")

thd = threading.Thread(target=print_sth)
thd.start()
thread test

 

 

단순하게 함수를 실행시키는거랑 뭐가 다르지? 할 수 있다. 이제 아래 내용을 확인해보자. 실행시키면 첫번째 함수가 다 수행되고, 이어서 다음 함수가 수행된다. 만약 동시에 수행하고 싶다면?

import threading
import time

def print_sth():
    for i in range(1,5):
        print(f"thread test : {i}")
        time.sleep(0.2)

print_sth()
print_sth()
thread test : 1
thread test : 2
thread test : 3
thread test : 4
thread test : 1
thread test : 2
thread test : 3
thread test : 4

 

 

아래처럼 별도의 thread를 두면 위에 실행중인 함수가 종료되지 않아도 다음 함수가 바로 병렬적으로 수행된다.

import threading
import time

def print_sth():
    for i in range(1,5):
        print(f"thread test : {i}")
        time.sleep(0.2)

thd1 = threading.Thread(target=print_sth)
thd1.start()
thd2 = threading.Thread(target=print_sth)
thd2.start()
thread test : 1
thread test : 1
thread test : 2
thread test : 2
thread test : 3
thread test : 3
thread test : 4
thread test : 4

 

 

이번에는 schedule 모듈을 통해 정해진 주기마다 특정 함수를 별도의 thread로 수행시킬수 있다. 알아두면 좋은듯 하다.

import threading
import time
import schedule

def print_sth():
    for i in range(1,5):
        print(f"thread test : {i}")
        time.sleep(0.2)

def schedule_start():
    thd1 = threading.Thread(target=print_sth)
    thd1.start()
    
schedule.every(3).seconds.do(schedule_start)
schedule.every(3).seconds.do(schedule_start)

while True:
    schedule.run_pending()
    time.sleep(1)
thread test : 1
thread test : 1
thread test : 2
thread test : 2
thread test : 3
thread test : 3
thread test : 4
thread test : 4

 

 

다음은 thread를 class에 적용하는 방법이다. 

import threading
import time

class Worker(threading.Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name          

    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 = f"*thread number : {i}"
    thd = Worker(name)             
    thd.start()                       
print("main thread end")

 

 

다음은 daemon thread이다. daemon thread는 garbage collection, 요청 처리, 리소스 청소resource cleanup와 같은 백그라운드 태스크를 실행하며 낮은 우선순위low priority를 가지고 있다. 일반 thread를 보조하기 때문에, daemon thread는 일반 쓰레드가 실행 중일 때에만 동작하며 일반 thread가 종료되면 daemon thread는 강제로 종료된다.

이전에는 start뒤에 end까지 출력 되었지만 daemon thread 설정을 위해 daemon = True로 설정했더니 end까지 기다리지 않고 바로 파이썬이 종료된다. (종료되지 않게 하려면 위에서처럼 schedule을 넣어두면 된다.)

import threading
import time

class Worker(threading.Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name          

    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 = f"*thread number : {i}"
    thd = Worker(name)               
    thd.daemon = True
    thd.start()                    
print("main thread end")
main thread start
sub thread start!  *thread number : 0
sub thread start!  *thread number : 1
sub thread start!  *thread number : 2
sub thread start!  *thread number : 3
sub thread start!  *thread number : 4
main thread end

 

 

이번에는 damnon에 이어서 join이다. join은 해당 thread의 종료를 기다려준다. 아래에서 보는 것처럼 thread 0번이 다 끝나면 1번이 시작되고 1번이 다 끝나면 2번이 시작되는 형태이다. 결과값을 보면서 기본 thread와 daemon과 join에 대해 차이점을 분명히 알아두자. join은 thread의 종료를 기다린다 했으니 당연히 무한 loop을 돌리면 안된다.

import threading
import time

class Worker(threading.Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name          

    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 = f"*thread number : {i}"
    thd = Worker(name)               
    thd.daemon = True
    thd.start()     
    thd.join()            
print("main thread end")
main thread start
sub thread start!  *thread number : 0
sub thread end!  *thread number : 0
sub thread start!  *thread number : 1
sub thread end!  *thread number : 1
sub thread start!  *thread number : 2
sub thread end!  *thread number : 2
sub thread start!  *thread number : 3
sub thread end!  *thread number : 3
sub thread start!  *thread number : 4
sub thread end!  *thread number : 4
main thread end

 

 

마지막으로 thread는 객체에 담을수도 있다. 아래 threads라는 객체에 thread를 담은 다음에 for문을 통해 join시킬 수 있다.

threads를 print해보면 어떤 thread가 담겼는지 확인이 가능하다.

import threading
import time

class Worker(threading.Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name          

    def run(self):
        print("sub thread start! ", threading.currentThread().getName())
        time.sleep(3)
        print("sub thread end! ", threading.currentThread().getName())

print("main thread start")
threads = []
for i in range(5):
    thread = Worker(i)
    thread.start()            
    threads.append(thread)
print(threads)
for thread in threads:
    thread.join()
print("main thread end")
main thread start
sub thread start!  0
sub thread start!  1
sub thread start!  2
sub thread start!  3
sub thread start!  4
[<Worker(0, started 7972)>, <Worker(1, started 6036)>, <Worker(2, started 6012)>, <Worker(3, started 9692)>, <Worker(4, started 9832)>]
sub thread end!  2
sub thread end!  1
sub thread end!  0
sub thread end!  3
sub thread end!  4
main thread end

 

끝!