Python 스레드 완료 대기 방법

PythonBeginner
지금 연습하기

소개

Python 스레드의 완료를 기다리는 방법을 숙달하는 것은 견고하고 신뢰할 수 있는 애플리케이션을 구축하는 데 필수적입니다. 멀티 스레드 프로그램에서 적절한 동기화는 작업이 올바른 순서로 완료되고 리소스가 효율적으로 사용되도록 보장합니다.

이 Lab 에서는 Python 스레드를 생성하고, 완료를 기다리고, 여러 스레드를 처리하는 방법을 배우게 됩니다. 이러한 기술은 적절한 동기화를 유지하면서 여러 작업을 동시에 수행할 수 있는 동시 애플리케이션을 개발하는 데 기본입니다.

첫 번째 Python 스레드 생성하기

Python 의 threading 모듈은 스레드를 생성하고 관리하는 간단한 방법을 제공합니다. 이 단계에서는 기본 스레드를 생성하고 동작을 관찰하는 방법을 배우게 됩니다.

스레드 이해하기

스레드는 프로그램 내에서 별도의 실행 흐름입니다. Python 스크립트를 실행하면 메인 스레드라고 하는 단일 스레드로 시작합니다. 추가 스레드를 생성하여 프로그램은 여러 작업을 동시에 수행할 수 있습니다.

스레드는 다음과 같은 경우에 유용합니다.

  • 메인 프로그램을 차단하지 않고 시간이 오래 걸리는 작업을 실행할 때
  • 성능 향상을 위해 작업을 병렬로 처리할 때
  • 서버 애플리케이션에서 여러 클라이언트 연결을 처리할 때

간단한 스레드 생성하기

스레드를 생성하고 시작하는 방법을 보여주는 간단한 Python 스크립트를 생성해 보겠습니다.

  1. "File" 메뉴를 클릭하고 "New File"을 선택한 다음 /home/labex/project 디렉토리에 simple_thread.py로 저장하여 편집기에서 새 파일을 엽니다.

  2. 파일에 다음 코드를 추가합니다.

import threading
import time

def print_numbers():
    """Function that prints numbers from 1 to 5 with a delay."""
    for i in range(1, 6):
        print(f"Number {i} from thread")
        time.sleep(1)  ## Sleep for 1 second

## Create a thread that targets the print_numbers function
number_thread = threading.Thread(target=print_numbers)

## Start the thread
print("Starting the thread...")
number_thread.start()

## Main thread continues execution
print("Main thread continues to run...")
print("Main thread is doing other work...")

## Sleep for 2 seconds to demonstrate both threads running concurrently
time.sleep(2)
print("Main thread finished its work!")
  1. Ctrl+S를 누르거나 "File" > "Save"를 클릭하여 파일을 저장합니다.

  2. 터미널을 열고 (아직 열려 있지 않은 경우) 다음을 실행하여 스크립트를 실행합니다.

python3 /home/labex/project/simple_thread.py

다음과 유사한 출력을 볼 수 있습니다.

Starting the thread...
Main thread continues to run...
Main thread is doing other work...
Number 1 from thread
Number 2 from thread
Main thread finished its work!
Number 3 from thread
Number 4 from thread
Number 5 from thread

무슨 일이 일어났는지 분석하기

이 예제에서:

  1. threadingtime 모듈을 가져왔습니다.
  2. 각 숫자 사이에 1 초의 지연을 두고 1 에서 5 까지 숫자를 출력하는 print_numbers() 함수를 정의했습니다.
  3. target 매개변수를 사용하여 실행할 함수를 지정하여 스레드 객체를 생성했습니다.
  4. start() 메서드를 사용하여 스레드를 시작했습니다.
  5. 메인 스레드는 실행을 계속하여 메시지를 출력하고 2 초 동안 sleep 했습니다.
  6. 메인 스레드와 숫자 스레드가 모두 동시에 실행되었으며, 이것이 출력이 인터리브된 이유입니다.

메인 스레드가 숫자 스레드가 모든 숫자를 출력하기 전에 완료되었음을 확인하십시오. 이는 스레드가 독립적으로 실행되기 때문이며, 기본적으로 Python 프로그램은 다른 스레드가 계속 실행 중이더라도 메인 스레드가 완료되면 종료됩니다.

다음 단계에서는 join() 메서드를 사용하여 스레드가 완료될 때까지 기다리는 방법을 배우게 됩니다.

join() 을 사용하여 스레드 완료를 기다리기

이전 단계에서는 메인 스레드와 독립적으로 실행되는 스레드를 생성했습니다. 그러나 프로그램의 나머지 부분을 진행하기 전에 스레드가 작업을 완료할 때까지 기다려야 하는 경우가 많습니다. 이때 join() 메서드가 유용합니다.

join() 메서드 이해하기

스레드 객체의 join() 메서드는 join() 메서드가 호출된 스레드가 종료될 때까지 호출하는 스레드 (일반적으로 메인 스레드) 를 차단합니다. 이는 다음과 같은 경우에 필수적입니다.

  • 메인 스레드가 작업자 스레드로부터 결과를 필요로 할 때
  • 프로그램 종료 전에 모든 스레드가 완료되도록 해야 할 때
  • 작업 순서가 애플리케이션 로직에 중요할 때

스레드 생성 및 완료 대기

join() 메서드를 사용하여 스레드가 완료될 때까지 기다리는 방법을 보여주기 위해 이전 예제를 수정해 보겠습니다.

  1. /home/labex/project 디렉토리에 join_thread.py라는 새 파일을 생성합니다.

  2. 파일에 다음 코드를 추가합니다.

import threading
import time

def calculate_sum(numbers):
    """Function that calculates the sum of numbers with a delay."""
    print("Starting the calculation...")
    time.sleep(3)  ## Simulate a time-consuming calculation
    result = sum(numbers)
    print(f"Calculation result: {result}")
    return result

## Create a list of numbers
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

## Create a thread that targets the calculate_sum function
calculation_thread = threading.Thread(target=calculate_sum, args=(numbers,))

## Start the thread
print("Main thread: Starting the calculation thread...")
calculation_thread.start()

## Do some other work in the main thread
print("Main thread: Doing some other work while waiting...")
for i in range(5):
    print(f"Main thread: Working... ({i+1}/5)")
    time.sleep(0.5)

## Wait for the calculation thread to complete
print("Main thread: Waiting for the calculation thread to finish...")
calculation_thread.join()
print("Main thread: Calculation thread has finished!")

## Continue with the main thread
print("Main thread: Continuing with the rest of the program...")
  1. 파일을 저장하고 다음 명령으로 실행합니다.
python3 /home/labex/project/join_thread.py

다음과 유사한 출력을 볼 수 있습니다.

Main thread: Starting the calculation thread...
Starting the calculation...
Main thread: Doing some other work while waiting...
Main thread: Working... (1/5)
Main thread: Working... (2/5)
Main thread: Working... (3/5)
Main thread: Working... (4/5)
Main thread: Working... (5/5)
Main thread: Waiting for the calculation thread to finish...
Calculation result: 55
Main thread: Calculation thread has finished!
Main thread: Continuing with the rest of the program...

join() 의 중요성

이 예제에서:

  1. 계산 (숫자 합산) 을 수행하는 스레드를 생성했습니다.
  2. 메인 스레드는 다른 작업을 동시에 수행했습니다.
  3. 메인 스레드가 계산이 완료되었는지 확인해야 할 때 calculation_thread.join()을 호출했습니다.
  4. join() 메서드는 계산 스레드가 완료될 때까지 메인 스레드가 대기하도록 했습니다.
  5. 계산 스레드가 완료된 후 메인 스레드는 실행을 계속했습니다.

이 패턴은 프로그램의 나머지 부분을 진행하기 전에 모든 스레드 작업이 완료되었는지 확인해야 할 때 매우 유용합니다. join()이 없으면 메인 스레드가 작업자 스레드가 작업을 완료하기 전에 계속 진행되어 종료될 수도 있습니다.

타임아웃과 함께 join() 사용하기

때로는 스레드를 기다리되 무기한으로 기다리고 싶지 않을 수 있습니다. join() 메서드는 대기할 최대 시간 (초) 을 지정하는 선택적 타임아웃 매개변수를 허용합니다.

이를 보여주기 위해 코드를 수정해 보겠습니다.

  1. /home/labex/project 디렉토리에 join_timeout.py라는 새 파일을 생성합니다.

  2. 다음 코드를 추가합니다.

import threading
import time

def long_running_task():
    """A function that simulates a very long-running task."""
    print("Long-running task started...")
    time.sleep(10)  ## Simulate a 10-second task
    print("Long-running task completed!")

## Create and start the thread
task_thread = threading.Thread(target=long_running_task)
task_thread.start()

## Wait for the thread to complete, but only for up to 3 seconds
print("Main thread: Waiting for up to 3 seconds...")
task_thread.join(timeout=3)

## Check if the thread is still running
if task_thread.is_alive():
    print("Main thread: The task is still running, but I'm continuing anyway!")
else:
    print("Main thread: The task has completed within the timeout period.")

## Continue with the main thread
print("Main thread: Continuing with other operations...")
## Let's sleep a bit to see the long-running task complete
time.sleep(8)
print("Main thread: Finished.")
  1. 파일을 저장하고 실행합니다.
python3 /home/labex/project/join_timeout.py

출력은 다음과 같아야 합니다.

Long-running task started...
Main thread: Waiting for up to 3 seconds...
Main thread: The task is still running, but I'm continuing anyway!
Main thread: Continuing with other operations...
Long-running task completed!
Main thread: Finished.

이 예제에서 메인 스레드는 작업 스레드가 완료될 때까지 최대 3 초 동안 기다립니다. 작업에 10 초가 걸리므로 메인 스레드는 타임아웃 후 계속 진행되는 반면, 작업 스레드는 백그라운드에서 계속 실행됩니다.

이 접근 방식은 스레드에 완료할 기회를 주고 싶지만 특정 시간이 지난 후에도 계속 진행해야 할 때 유용합니다.

여러 스레드 작업하기

실제 애플리케이션에서는 여러 스레드를 동시에 작업해야 하는 경우가 많습니다. 이 단계에서는 Python 에서 여러 스레드를 생성, 관리 및 동기화하는 방법을 배우게 됩니다.

여러 스레드 생성하기

여러 유사한 작업을 처리할 때는 일반적으로 여러 스레드를 생성하여 동시에 처리합니다. 이는 특히 파일 다운로드 또는 네트워크 요청과 같은 I/O 바운드 작업의 경우 성능을 크게 향상시킬 수 있습니다.

여러 스레드를 사용하여 작업 목록을 처리하는 예제를 만들어 보겠습니다.

  1. /home/labex/project 디렉토리에 multiple_threads.py라는 새 파일을 생성합니다.

  2. 다음 코드를 추가합니다.

import threading
import time
import random

def process_task(task_id):
    """Function to process a single task."""
    print(f"Starting task {task_id}...")
    ## Simulate variable processing time
    processing_time = random.uniform(1, 3)
    time.sleep(processing_time)
    print(f"Task {task_id} completed in {processing_time:.2f} seconds.")
    return task_id

## List of tasks to process
tasks = list(range(1, 6))  ## Tasks with IDs 1 through 5

## Create a list to store our threads
threads = []

## Create and start a thread for each task
for task_id in tasks:
    thread = threading.Thread(target=process_task, args=(task_id,))
    threads.append(thread)
    print(f"Created thread for task {task_id}")
    thread.start()

print(f"All {len(threads)} threads have been started")

## Wait for all threads to complete
for thread in threads:
    thread.join()

print("All tasks have been completed!")
  1. 파일을 저장하고 실행합니다.
python3 /home/labex/project/multiple_threads.py

출력은 무작위 처리 시간으로 인해 매번 다르지만 다음과 유사해야 합니다.

Created thread for task 1
Starting task 1...
Created thread for task 2
Starting task 2...
Created thread for task 3
Starting task 3...
Created thread for task 4
Starting task 4...
Created thread for task 5
Starting task 5...
All 5 threads have been started
Task 1 completed in 1.23 seconds.
Task 3 completed in 1.45 seconds.
Task 2 completed in 1.97 seconds.
Task 5 completed in 1.35 seconds.
Task 4 completed in 2.12 seconds.
All tasks have been completed!

실행 흐름 이해하기

이 예제에서:

  1. 무작위 기간으로 작업을 시뮬레이션하는 process_task() 함수를 정의했습니다.
  2. 작업 ID 목록 (1~5) 을 생성했습니다.
  3. 각 작업에 대해 스레드를 생성하고 목록에 저장한 다음 시작했습니다.
  4. 모든 스레드를 시작한 후 join()을 사용하여 각 스레드가 완료될 때까지 기다리는 두 번째 루프를 사용했습니다.
  5. 모든 스레드가 완료된 후에만 최종 메시지를 인쇄했습니다.

이 패턴은 병렬로 처리할 수 있는 독립적인 작업 배치가 있는 경우 매우 유용합니다.

스레드 풀 실행기 (Thread Pool Executors)

보다 고급 스레드 관리를 위해 Python 의 concurrent.futures 모듈은 ThreadPoolExecutor 클래스를 제공합니다. 이는 재사용할 수 있는 작업자 스레드 풀을 생성하며, 각 작업에 대해 스레드를 생성하고 삭제하는 것보다 더 효율적입니다.

스레드 풀을 사용하여 예제를 다시 작성해 보겠습니다.

  1. /home/labex/project 디렉토리에 thread_pool.py라는 새 파일을 생성합니다.

  2. 다음 코드를 추가합니다.

import concurrent.futures
import time
import random

def process_task(task_id):
    """Function to process a single task."""
    print(f"Starting task {task_id}...")
    ## Simulate variable processing time
    processing_time = random.uniform(1, 3)
    time.sleep(processing_time)
    print(f"Task {task_id} completed in {processing_time:.2f} seconds.")
    return f"Result of task {task_id}"

## List of tasks to process
tasks = list(range(1, 6))  ## Tasks with IDs 1 through 5

## Create a ThreadPoolExecutor
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
    ## Submit all tasks and store the Future objects
    print(f"Submitting {len(tasks)} tasks to the thread pool with 3 workers...")
    future_to_task = {executor.submit(process_task, task_id): task_id for task_id in tasks}

    ## As each task completes, get its result
    for future in concurrent.futures.as_completed(future_to_task):
        task_id = future_to_task[future]
        try:
            result = future.result()
            print(f"Got result from task {task_id}: {result}")
        except Exception as e:
            print(f"Task {task_id} generated an exception: {e}")

print("All tasks have been processed!")
  1. 파일을 저장하고 실행합니다.
python3 /home/labex/project/thread_pool.py

출력은 무작위 처리 시간으로 인해 다시 달라지지만 다음과 유사해야 합니다.

Submitting 5 tasks to the thread pool with 3 workers...
Starting task 1...
Starting task 2...
Starting task 3...
Task 2 completed in 1.15 seconds.
Starting task 4...
Got result from task 2: Result of task 2
Task 1 completed in 1.82 seconds.
Starting task 5...
Got result from task 1: Result of task 1
Task 3 completed in 2.25 seconds.
Got result from task 3: Result of task 3
Task 4 completed in 1.45 seconds.
Got result from task 4: Result of task 4
Task 5 completed in 1.67 seconds.
Got result from task 5: Result of task 5
All tasks have been processed!

스레드 풀의 장점

스레드 풀 접근 방식은 몇 가지 이점을 제공합니다.

  1. 리소스 관리: 동시에 실행할 수 있는 스레드 수를 제한하여 시스템 리소스 고갈을 방지합니다.
  2. 작업 스케줄링: 스레드를 사용할 수 있게 되면 자동으로 작업 스케줄링을 처리합니다.
  3. 결과 수집: 완료된 작업에서 결과를 수집하는 편리한 방법을 제공합니다.
  4. 예외 처리: 스레드에서 예외를 처리하는 것을 더 간단하게 만듭니다.

이 예제에서는 max_workers=3으로 설정했습니다. 즉, 5 개의 작업이 있더라도 한 번에 3 개의 스레드만 실행됩니다. 스레드가 작업을 완료하면 나머지 작업에 재사용됩니다.

스레드 풀은 동시에 실행하려는 스레드보다 훨씬 더 많은 작업이 있거나 작업이 지속적으로 생성되는 경우 특히 유용합니다.

스레드 타임아웃 및 데몬 스레드

이 마지막 단계에서는 스레드 관리의 두 가지 중요한 개념인 타임아웃 설정 및 데몬 스레드 사용에 대해 배우게 됩니다. 이러한 기술을 통해 스레드가 메인 프로그램과 상호 작용하는 방식을 더 잘 제어할 수 있습니다.

스레드 타임아웃 사용하기

Step 2 에서 배운 것처럼 join() 메서드는 타임아웃 매개변수를 허용합니다. 이는 스레드가 완료될 때까지 기다리되 특정 시점까지만 기다리려는 경우에 유용합니다.

타임아웃으로 데이터를 가져오려고 시도하는 함수를 구현하는 더 실용적인 예제를 만들어 보겠습니다.

  1. /home/labex/project 디렉토리에 thread_with_timeout.py라는 새 파일을 생성합니다.

  2. 다음 코드를 추가합니다.

import threading
import time
import random

def fetch_data(data_id):
    """Simulate fetching data that might take varying amounts of time."""
    print(f"Fetching data #{data_id}...")

    ## Simulate different fetch times, occasionally very long
    fetch_time = random.choices([1, 8], weights=[0.8, 0.2])[0]
    time.sleep(fetch_time)

    if fetch_time > 5:  ## Simulate a slow fetch
        print(f"Data #{data_id}: Fetch took too long!")
        return None
    else:
        print(f"Data #{data_id}: Fetch completed in {fetch_time} seconds!")
        return f"Data content for #{data_id}"

def fetch_with_timeout(data_id, timeout=3):
    """Fetch data with a timeout."""
    result = [None]  ## Using a list to store result from the thread

    def target_func():
        result[0] = fetch_data(data_id)

    ## Create and start the thread
    thread = threading.Thread(target=target_func)
    thread.start()

    ## Wait for the thread with a timeout
    thread.join(timeout=timeout)

    if thread.is_alive():
        print(f"Data #{data_id}: Fetch timed out after {timeout} seconds!")
        return "TIMEOUT"
    else:
        return result[0]

## Try to fetch several pieces of data
for i in range(1, 6):
    print(f"\nAttempting to fetch data #{i}")
    result = fetch_with_timeout(i, timeout=3)
    if result == "TIMEOUT":
        print(f"Main thread: Fetch for data #{i} timed out, moving on...")
    elif result is None:
        print(f"Main thread: Fetch for data #{i} completed but returned no data.")
    else:
        print(f"Main thread: Successfully fetched: {result}")

print("\nAll fetch attempts completed!")
  1. 파일을 저장하고 실행합니다.
python3 /home/labex/project/thread_with_timeout.py

출력은 다를 수 있지만 다음과 유사해야 합니다.

Attempting to fetch data #1
Fetching data #1...
Data #1: Fetch completed in 1 seconds!
Main thread: Successfully fetched: Data content for #1

Attempting to fetch data #2
Fetching data #2...
Data #2: Fetch completed in 1 seconds!
Main thread: Successfully fetched: Data content for #2

Attempting to fetch data #3
Fetching data #3...
Data #3: Fetch timed out after 3 seconds!
Main thread: Fetch for data #3 timed out, moving on...
Data #3: Fetch took too long!

Attempting to fetch data #4
Fetching data #4...
Data #4: Fetch completed in 1 seconds!
Main thread: Successfully fetched: Data content for #4

Attempting to fetch data #5
Fetching data #5...
Data #5: Fetch completed in 1 seconds!
Main thread: Successfully fetched: Data content for #5

All fetch attempts completed!

이 예제는 다음을 보여줍니다.

  1. 데이터를 가져오려고 시도하고 느릴 수 있는 함수
  2. 타임아웃을 사용하여 스레딩을 사용하는 래퍼 함수
  3. 타임아웃을 적절하게 처리하고 다른 작업을 계속하는 방법

데몬 스레드 이해하기

Python 에서 데몬 스레드는 백그라운드에서 실행되는 스레드입니다. 데몬 스레드와 비데몬 스레드의 주요 차이점은 Python 이 종료되기 전에 데몬 스레드가 완료될 때까지 기다리지 않는다는 것입니다. 이는 프로그램 종료를 방해해서는 안 되는 백그라운드 작업을 수행하는 스레드에 유용합니다.

데몬 스레드를 보여주는 예제를 만들어 보겠습니다.

  1. /home/labex/project 디렉토리에 daemon_threads.py라는 새 파일을 생성합니다.

  2. 다음 코드를 추가합니다.

import threading
import time

def background_task(name, interval):
    """A task that runs in the background at regular intervals."""
    count = 0
    while True:
        count += 1
        print(f"{name}: Iteration {count} at {time.strftime('%H:%M:%S')}")
        time.sleep(interval)

def main_task():
    """The main task that runs for a set amount of time."""
    print("Main task: Starting...")
    time.sleep(5)
    print("Main task: Completed!")

## Create two background threads
print("Creating background monitoring threads...")
monitor1 = threading.Thread(target=background_task, args=("Monitor-1", 1), daemon=True)
monitor2 = threading.Thread(target=background_task, args=("Monitor-2", 2), daemon=True)

## Start the background threads
monitor1.start()
monitor2.start()

print("Background monitors started, now starting main task...")

## Run the main task
main_task()

print("Main task completed, program will exit without waiting for daemon threads.")
print("Daemon threads will be terminated when the program exits.")
  1. 파일을 저장하고 실행합니다.
python3 /home/labex/project/daemon_threads.py

출력은 다음과 유사해야 합니다.

Creating background monitoring threads...
Background monitors started, now starting main task...
Main task: Starting...
Monitor-1: Iteration 1 at 14:25:10
Monitor-2: Iteration 1 at 14:25:10
Monitor-1: Iteration 2 at 14:25:11
Monitor-1: Iteration 3 at 14:25:12
Monitor-2: Iteration 2 at 14:25:12
Monitor-1: Iteration 4 at 14:25:13
Monitor-1: Iteration 5 at 14:25:14
Monitor-2: Iteration 3 at 14:25:14
Main task: Completed!
Main task completed, program will exit without waiting for daemon threads.
Daemon threads will be terminated when the program exits.

이 예제에서:

  1. 정기적으로 메시지를 인쇄하면서 지속적으로 실행되는 두 개의 데몬 스레드를 생성했습니다.
  2. 스레드를 생성할 때 daemon=True를 설정하여 데몬 스레드로 표시했습니다.
  3. 메인 스레드는 5 초 동안 실행된 다음 종료됩니다.
  4. 메인 스레드가 종료되면 프로그램이 종료되고 데몬 스레드도 자동으로 종료됩니다.

비데몬 스레드 vs. 데몬 스레드

차이점을 더 잘 이해하기 위해 데몬 스레드와 비데몬 스레드를 비교하는 예제를 하나 더 만들어 보겠습니다.

  1. /home/labex/project 디렉토리에 daemon_comparison.py라는 새 파일을 생성합니다.

  2. 다음 코드를 추가합니다.

import threading
import time

def task(name, seconds, daemon=False):
    """A task that runs for a specified amount of time."""
    print(f"{name} starting {'(daemon)' if daemon else '(non-daemon)'}")
    time.sleep(seconds)
    print(f"{name} finished after {seconds} seconds")

## Create a non-daemon thread that runs for 8 seconds
non_daemon_thread = threading.Thread(
    target=task,
    args=("Non-daemon thread", 8, False),
    daemon=False  ## This is the default, so it's not actually needed
)

## Create a daemon thread that runs for 8 seconds
daemon_thread = threading.Thread(
    target=task,
    args=("Daemon thread", 8, True),
    daemon=True
)

## Start both threads
non_daemon_thread.start()
daemon_thread.start()

## Let the main thread run for 3 seconds
print("Main thread will run for 3 seconds...")
time.sleep(3)

## Check which threads are still running
print("\nAfter 3 seconds:")
print(f"Daemon thread is alive: {daemon_thread.is_alive()}")
print(f"Non-daemon thread is alive: {non_daemon_thread.is_alive()}")

print("\nMain thread is finishing. Here's what will happen:")
print("1. The program will wait for all non-daemon threads to complete")
print("2. Daemon threads will be terminated when the program exits")

print("\nWaiting for non-daemon threads to finish...")
## We don't need to join the non-daemon thread, Python will wait for it
## But we'll explicitly join it for clarity
non_daemon_thread.join()
print("All non-daemon threads have finished, program will exit now.")
  1. 파일을 저장하고 실행합니다.
python3 /home/labex/project/daemon_comparison.py

출력은 다음과 같아야 합니다.

Non-daemon thread starting (non-daemon)
Daemon thread starting (daemon)
Main thread will run for 3 seconds...

After 3 seconds:
Daemon thread is alive: True
Non-daemon thread is alive: True

Main thread is finishing. Here's what will happen:
1. The program will wait for all non-daemon threads to complete
2. Daemon threads will be terminated when the program exits

Waiting for non-daemon threads to finish...
Non-daemon thread finished after 8 seconds
All non-daemon threads have finished, program will exit now.

주요 관찰 사항:

  1. 두 스레드가 모두 시작되어 동시에 실행됩니다.
  2. 3 초 후 두 스레드가 모두 계속 실행됩니다.
  3. 프로그램은 비데몬 스레드가 완료될 때까지 기다립니다 (8 초 후).
  4. 프로그램이 종료될 때 데몬 스레드는 여전히 실행 중이지만 종료됩니다.
  5. 데몬 스레드는 프로그램이 종료될 때 종료되므로 완료 메시지를 인쇄하지 않습니다.

데몬 스레드를 사용해야 하는 경우

데몬 스레드는 다음과 같은 경우에 유용합니다.

  • 백그라운드 모니터링 작업
  • 정리 작업
  • 프로그램 실행 시간 동안 실행되어야 하지만 종료를 방해하지 않는 서비스
  • 정기적으로 이벤트를 트리거하는 타이머 스레드

비데몬 스레드는 다음과 같은 경우에 적합합니다.

  • 완료되어야 하는 중요한 작업
  • 중단되어서는 안 되는 작업
  • 프로그램이 종료되기 전에 깔끔하게 완료되어야 하는 작업

각 유형을 언제 사용해야 하는지 이해하는 것은 강력한 멀티 스레드 애플리케이션을 설계하는 데 중요한 부분입니다.

요약

이 랩에서는 Python 스레드로 작업하고 스레드가 완료될 때까지 기다리는 데 필요한 필수 기술을 배웠습니다. 다음은 다룬 주요 개념에 대한 요약입니다.

  1. 스레드 생성 및 시작: 스레드 객체를 생성하고, 대상 함수를 지정하고, start() 메서드를 사용하여 실행을 시작하는 방법을 배웠습니다.

  2. join() 을 사용하여 스레드 대기: 메인 프로그램을 계속하기 전에 스레드가 완료될 때까지 기다리는 데 join() 메서드를 사용하여 적절한 동기화를 보장하는 방법을 배웠습니다.

  3. 여러 스레드 작업: 수동으로, 그리고 보다 효율적인 스레드 관리를 위해 ThreadPoolExecutor 클래스를 사용하여 여러 스레드를 생성하고 관리하는 연습을 했습니다.

  4. 스레드 타임아웃 및 데몬 스레드: 스레드 작업에 대한 타임아웃 설정 및 백그라운드 작업에 데몬 스레드를 사용하는 것을 포함한 고급 주제를 탐구했습니다.

이러한 기술은 Python 에서 멀티 스레드 애플리케이션을 개발하기 위한 기반을 제공합니다. 멀티 스레딩을 사용하면 프로그램이 여러 작업을 동시에 수행하여 성능과 응답성을 향상시킬 수 있으며, 특히 I/O 바운드 작업에 유용합니다.

스레드로 계속 작업할 때 다음 모범 사례를 기억하십시오.

  • CPU 바운드 작업이 아닌 I/O 바운드 작업에 스레드를 사용하십시오 (후자의 경우 멀티 프로세싱을 고려하십시오).
  • 공유 리소스를 염두에 두고 적절한 동기화 메커니즘을 사용하십시오.
  • 여러 스레드를 관리하기 위해 ThreadPoolExecutor와 같은 상위 수준 추상화를 사용하는 것을 고려하십시오.
  • 프로그램 종료를 방해해서는 안 되는 백그라운드 작업에 데몬 스레드를 사용하십시오.

이러한 기술과 실습을 통해 이제 멀티 스레딩 기술을 사용하여 보다 효율적이고 반응성이 뛰어난 Python 애플리케이션을 구축할 수 있습니다.