본문 바로가기
Python/Tip & Etc

python Thread(쓰레드) 병렬처리

by To올라운더 2023. 11. 17.
반응형

파이썬은 한줄씩 코드를 처리하는 인터프리터 방식의 프로그래밍 언어이다.

 

한줄씩 처리한다는 의미대로 해석해보면 2개 이상의 코드를 실행(병렬처리)할 수 없다는 의미인데,

아래 코드를 실행해보면 당연하게도 Start 라는 print 문을 실행한 뒤, 5초 동안의 대기 후 for 문이 실행된다.

import time

delay_sec = 5
print('Start')
time.sleep(delay_sec)
print(f'This Delay Time {delay_sec} Second')

for i in range(1, 11):
    print(f'run {i}')
    time.sleep(1)

 

 - 오늘 포스팅은 빨간색과 보라색 영역을 동시에 실행하는 쓰레드(Thread / 병렬처리) 에 대한 포스팅이다.

 

 

1. Thread

 - Thread는 병렬 처리를 하는 방법 중 하나이고, 오늘은 깊이 있게 다루기 보다는 어떻게 사용하는지에 대한

   목적을 가지고 진행할 예정이다.

 - GIL(Global Interpreter Lock)이나 멀티프로세싱과 같은 부분은 다음에 기회가 되면 별도의 포스팅으로 진행하겠습니다.

 - Thread는 간단히 이해하려면 위의 빨간색 블럭의 코드와 보라색 블럭의 코드를 동시에 진행하는 것을 말합니다.

 - 결과를 먼저 확인해보면, 우리가 위에서 진행한 당연하다고 생각했던 결과가 아닌

   빨간색 코드가 실행되는 동안(delay 5초)  보라색 코드도 함께 진행되었다는 것을 볼 수 있다.

 

 

 

2. 코드 알아보기

import threading, time

delay_sec = 5


def set_delay(second):
    print('Start')
    time.sleep(second)
    print(f'This Thread Delay Time {delay_sec} Second')


def print_run():

    for i in range(1, 11):
        print(f'run {i}')
        time.sleep(1)

t1 = threading.Thread(target=set_delay, args=(5,))
t1.start()

t2 = threading.Thread(target=print_run)
t2.start()

print('END')
반응형

 

- 먼저 가장 중요한 threading.Thread 라이브러리를 사용한다. 

 

- 동시에 실행할 코드 블럭을 각각 함수(또는 클래스)로 묶고, (def set_delay(), def print_run())

   병렬로 처리할 t1, t2의 thread 객체를 생성한다.

 

 - 각각의 thread 를 생성할 때, 처리해야할 매서드를 할당해주면(target = 매서드)

   준비는 끝이난다.(여기에서 생성한 객체를 서브 쓰레드라고 부른다.)

 

 - 해당 객체를 start 매서드로 시작해주면 2가지 함수가 함께 진행되는 것을 볼 수 있다.

 

 - 여기서 상황에 따라 몇 가지 옵션을 더 추가할 수 있는데 가장 대표적인 기능이 daemon 이다.

 

3. daemon

 - daemon 의 기본 값은 False 인데, 메인 쓰레드의 동작이 끝나면 해당 동작이 멈출지에 대한 조건이다.

 - t1, t2를 서브 쓰레드라고 부른다고 하였는데, 메인 쓰레드는 전체 코드를 실행시키는 쓰레드가 메인 쓰레드이다.

  그래서 메인 쓰레드는 t1과 t2, 마지막으로 print('END')를 순차적으로 실행하는데,

 

 - t1과 t2의 daemon이 True이면 메인 쓰레드가 종료될 때 두개의 서브 쓰레드도 종료가 된다.

   아래의 결과표를 보면 메인 쓰레드가 진행하는 순서에 따라 t1이 실행되며, 'Start' 를 출력하고,

   t2가 실행되며 'run 1'을 출력한 뒤. 메인쓰레드가 'END' 를 출력하면, 메인 쓰레드는 끝이난다.

   메인 쓰레드가 끝이 났기 때문에 t1과 t2도 더이상의 동작 없이 종료된다.

 

  - 반면 t1과 t2가 False 인 경우를 확인해보면,

    t1이 실행되며 'Start'를 출력하고, t2가 실행되며 'run 1'을 출력한 뒤, 메인 쓰레드의 'END'를 출력한다.

    여기까지는 t1, t2가 True일 때와 출력 값이 동일하지만,

    t1, t2는 메인쓰레드의 종료와 무관하기 때문에 자신에게 남은 작업이 끝날 때까지 실행을 지속한다.  

 

  - 세번째로 t1이 False, t2가 True 인 경우를 확인해보면,

    t1의 작업 예상 시간이 5초, t2가 10초로 t1의 작업 시간이 t2보다 짧다.

    t1은 메인 쓰레드가 종료되어도 계속 진행, t2는 메인 쓰레드가 종료되면 종료되는 설정이므로

    실행 결과를 보면 메인 쓰레드가 진행되는 t1(Start) -> t2(run 1) -> Main(END) 까지 동작한 뒤

    t1은 메인 쓰레드의 종료와 무관하므로 계속 작업을 진행한다.

    메인 쓰레드에 속한 t1이 종료되지 않았기 때문에 t2도 멈추지 않고 진행을 한다.

    그러다 t1이 종료되면 메인쓰레드가 종료되기 때문에 t2도 run5 까지 출력한 이후 같이 종료를 한다.

 

  - 반대의 경우인 t1 True, t2 False 인 경우는 t2의 작업 시간이 더 길기 때문에 t1 False, t2 False와 동일한 결과가 나타난다.

 

t1 t2 결과
False False
False True
True False
True True
 

 

 

4. join

 - 위에서 서브 쓰레드는 메인 쓰레드가 진행된 이후 daemon 속성에 따라 지속 여부가 달라진다고 이야기 하였고,

  그 결과 메인 쓰레드에 포함되는 print('END')가  t1.start()와 t2.start() 이후에 실행되는 것을 볼 수 있었다.

 - 그런데 t1과 t2를 완료하고 나머지 메인 쓰레드를 진행할 수는 없을까? 

 - 그 방법이 바로 join이다.

 

 - 사용법은 나중에 실행한 메인 쓰레드 위에 쓰레드명.join()을 추가해주기만 하면 된다.

import threading, time

delay_sec = 5


def set_delay(second):
    print('Start')
    time.sleep(second)
    print(f'This Thread Delay Time {delay_sec} Second')


def print_run():

    for i in range(1, 11):
        print(f'run {i}')
        time.sleep(1)

t1 = threading.Thread(target=set_delay, args=(5,))
t1.start()

t2 = threading.Thread(target=print_run)
t2.start()

t1.join()
t2.join()

print('END')

 

 - 24~25라인의 t1.join(), t2.join() 이라는 단 두줄의 추가로 메인쓰레드에 포함된 print('END')의 위치가 바뀐것을 볼 수 있다.

 

 

5. 점검하기

 - 쓰레드를 이해했다면 아래와 같은 조건의 코드와 결과를 작성할 수 있을 것이다.

 

 Question.

 A선수와 B 선수가 100m 달리기를 한다. 두 선수는 컨디션에 따라 결과를 예측하기 어려운 라이벌로 A 선수는 평균 4~7m/s, B선수는 평균 5~6m/s의 기록을 가지고 있다.

오늘 두 선수의 속도는 최소값과 최대 값 사이의 임의의 정수 값을 가질 때, 경기 진행 과정을 중계하는 코드를 작성해보세요. 

예시 결과 )

홍길동 선수 Start
전우치 선수 Start
1초 : 전우치 선수가 6m 지점을 지나갑니다.
1초 : 홍길동 선수가 7m 지점을 지나갑니다.
2초 : 전우치 선수가 12m 지점을 지나갑니다.
2초 : 홍길동 선수가 14m 지점을 지나갑니다.
3초 : 홍길동 선수가 21m 지점을 지나갑니다.
3초 : 전우치 선수가 18m 지점을 지나갑니다.
4초 : 홍길동 선수가 28m 지점을 지나갑니다.
4초 : 전우치 선수가 24m 지점을 지나갑니다.
5초 : 홍길동 선수가 35m 지점을 지나갑니다.
5초 : 전우치 선수가 30m 지점을 지나갑니다.
6초 : 전우치 선수가 36m 지점을 지나갑니다.
6초 : 홍길동 선수가 42m 지점을 지나갑니다.
7초 : 전우치 선수가 42m 지점을 지나갑니다.
7초 : 홍길동 선수가 49m 지점을 지나갑니다.
8초 : 홍길동 선수가 56m 지점을 지나갑니다.
8초 : 전우치 선수가 48m 지점을 지나갑니다.
9초 : 전우치 선수가 54m 지점을 지나갑니다.
9초 : 홍길동 선수가 63m 지점을 지나갑니다.
10초 : 홍길동 선수가 70m 지점을 지나갑니다.
10초 : 전우치 선수가 60m 지점을 지나갑니다.
11초 : 전우치 선수가 66m 지점을 지나갑니다.
11초 : 홍길동 선수가 77m 지점을 지나갑니다.
12초 : 홍길동 선수가 84m 지점을 지나갑니다.
12초 : 전우치 선수가 72m 지점을 지나갑니다.
13초 : 전우치 선수가 78m 지점을 지나갑니다.
13초 : 홍길동 선수가 91m 지점을 지나갑니다.
14초 : 전우치 선수가 84m 지점을 지나갑니다.
14초 : 홍길동 선수가 98m 지점을 지나갑니다.
15초 : 전우치 선수가 90m 지점을 지나갑니다.
15초 : 홍길동 선수가 105m 지점을 지나갑니다.
홍길동 선수가 결승선에 도착했습니다.        
16초 : 전우치 선수가 96m 지점을 지나갑니다.
17초 : 전우치 선수가 102m 지점을 지나갑니다.
전우치 선수가 결승선에 도착했습니다.

 

 

 

 -> 결과 및 해설은 다음 포스팅에서 작성해보겠습니다.

2023.11.18 - [Python/Tip & Etc] - python Thread(쓰레드) 병렬처리2(답안)

 

반응형