Python

[python] Multi-Thread 동시성/병렬성 프로그래밍(시간단축)

앳홍 2023. 8. 11. 19:39
반응형

Thread

  • 프로세스 내에서 실행되는 흐름의 단위
  • 프로세스 내에서 작업을 수행하는 주체이며 모든 프로세스에는 1개 이상의 쓰레드가 존재하여 작업을 수행
  • 일반적으로는 한 프로그램당 하나의 스레드를 가지나, 프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행가능
  • 둘 이상의 스레드를 동시에 실행한다면 이를 멀티스레드(Multi-Thread)라고 함
  • 스레드는 프로세스 내에서 stack만 따로 할당 받고, code, data, heap 영역은 공유
  • 프로세스 내의 주소 공간이나 자원들(Heap 공간)을 같은 프로세스 내에 스레드끼리 공유하면서 실행
  • 프로세스의 자원을 이용해서 작업을 수행

Multi-Thread 란?

  • 하나의 프로세스를 다수의 실행 단위로 구분
  • 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업 수행
  • 사실, CPU의 코어가 아주 짧은 시간 동안 여러 작업을 번갈아 가며 수행함으로써 여러 작업들이 모두 동시에 수행되는 것처럼 보임

 

  • target : 스레드에서 실행할 함수
  • args : target에 넘겨질 인자(여러개 가능)
  • daemon : 데몬 실행 여부. 데몬으로 실행되는 스레드는 프로세스가 종료될 때 즉각 중단

 

필요한 모듈 가져오기
from datetime import datetime
import threading

 

예제 소스코드
test2_lst = ['a','b','c']
def test(test2):
    print(test2)
    for test1 in range(5):
        print("test_",test1)

일단 'a','b','c' 를 담은 test2_lst 라는 리스트를 만들었다.
그리고 test2_lst를 for 문으로 하나씩 받아오는 test2 를 인자로 받는 test 함수를 생성
해당 함수는 test2 한개가 차례로 실행 될때마다 range(5)를 for문으로 하나씩 받고있는 test1을 하나씩 받아오게 했다.
 

test1 출력 결과

test2 출력 결과

 

main 함수 생성

main 함수를 생성해서 thread 를 구성한다.
위의 test 함수에서 받은 인자는 thread 선언 할 때 'for test2 in test_lst2'에서 만들었다. 

def main():
    tasks = [threading.Thread(target=test, args=(test2,)) for test2 in test_lst2]
    main_start_now = datetime.now()
    print(
        "Main-Thread : before creating thread",
        main_start_now.strftime("%Y-%m-%d %H:%M:%S"),
    )

    """tasks 하나씩 불러와서 start (task1.start(), task2.start(), task3.start()..)"""
    for t in tasks:
        t.start()
    main_join_now = datetime.now()

    """task 하나씩 불러와서 join"""
    for t in tasks:
        t.join()

    main_join_now = datetime.now()
    print("Main-Thread : joined thread", main_join_now.strftime("%Y-%m-%d %H:%M:%S"))
    print()
    print(f"thread t is alive: {t.is_alive()}")  # thread 살아있는 지 확인
    print(
        "=== The End ======================================================================="
    )

위의 소스코드 중 이부분은 동시실행시킬 작업이 여러 개일수록 유용하게 사용할 수 있다. 

    for t in tasks:
        t.start()
    main_join_now = datetime.now()

    for t in tasks:
        t.join()

아래의 소스코드는 위의 소스코드와 같은 결과를 출력하는 원래 여러 개 작업 설정하는 multi-threading 방법이다.

def main_2():
    task1 = threading.Thread(target=test, args=("a",))
    task2 = threading.Thread(target=test, args=("b",))
    task3 = threading.Thread(target=test, args=("c",))
    main_start_now = datetime.now()
    print(
        "Main-Thread : before creating thread",
        main_start_now.strftime("%Y-%m-%d %H:%M:%S"),
    )

    task1.start()
    task2.start()
    task3.start()
    main_join_now = datetime.now()
    
    task1.join()
    task2.join()
    task3.join()

    main_join_now = datetime.now()
    print("Main-Thread : joined thread", main_join_now.strftime("%Y-%m-%d %H:%M:%S"))
    print()
    print(f"thread t is alive: {task1.is_alive()}")  # thread 살아있는 지 확인
    print(f"thread t is alive: {task2.is_alive()}") 
    print(f"thread t is alive: {task3.is_alive()}")
    print(
        "=== The End ======================================================================="
    )
    
    if __name__ == "__main__":
    main_2()

 
결과는 아래와 같이 나온다. 
args에 인자로받은 각각의 (a,b,c) 작업이 마치고 join으로 인해 마지막 작업이 다 마칠때까지 기다린 후 마지막에 같이 종료한다.

 
 


그렇다면 이번엔 빈 thread 리스트를 생성하고 append()로 요소들을 넣어서 모두 실행하도록 해보자.

for 문처럼 한 개씩 완료되는 방법
def main_3():
    thread= []
    for test2 in test2_lst:
        tasks = threading.Thread(target=test, args=(test2,))
        main_start_now = datetime.now()
        print(
            "Main-Thread : before creating thread",
            main_start_now.strftime("%Y-%m-%d %H:%M:%S"),
        )

        tasks.start()
        thread.append(tasks)
        main_join_now = datetime.now()

        for t in thread:
            t.join()

        main_join_now = datetime.now()
        print("Main-Thread : joined thread", main_join_now.strftime("%Y-%m-%d %H:%M:%S"))
        print()
        print(f"thread t is alive: {t.is_alive()}")  # thread 살아있는 지 확인
        print(
            "=== The End ======================================================================="
        )
    
if __name__ == "__main__":
    main_3()

하나의 작업이 수행될때 모든 코드가 다 돌고 다음 작업에 들어가게된다.