Computer >> 컴퓨터 >  >> 프로그래밍 >> Python

Python 스레드로 동시성(Concurrency) 구현하는 방법 완벽 가이드


소개

Python은 스레드(thread), 서브프로세스(subprocess), 제너레이터(generator) 등 다양한 방식으로 동시성 프로그래밍을 구현할 수 있습니다. 본격적으로 스레드를 구현하기 전에, 동시성이 정확히 무엇인지 먼저 이해해 보겠습니다.

동시성(concurrency)이란 하나의 프로그램 내부에서 여러 개의 서로 다른 실행 경로를 열어두는 논리적 구조를 의미합니다. 여기에는 독립적인 I/O 스트림 처리, SQL 쿼리 실행 등이 포함되며, 각 작업이 마치 동시에 실행되면서도 서로 독립적으로 동작하는 것처럼 보이도록 만듭니다.

구현 방법

먼저 웹사이트 URL 목록을 순차적으로 처리하는 단일 스레드 프로그램을 만들어 보고, 이후 스레딩 개념을 활용해 프로그램 속도를 어떻게 개선할 수 있는지 살펴보겠습니다.

# Step 1 - 오늘 방문하고 싶은 웹사이트 URL 목록 만들기
import requests

tutorialpoints_url = ['https://www.tutorialspoint.com/python/index.htm',
'https://www.tutorialspoint.com/cplusplus/index.htm',
'https://www.tutorialspoint.com/java/index.htm',
'https://www.tutorialspoint.com/html/index.htm',
'https://www.tutorialspoint.com/cprogramming/index.htm']


# 전달된 URL에 요청을 보내고 상태 코드를 반환하는 함수
def visit_site(site_url):
"""
웹사이트 URL에 GET 요청을 보내고 응답 정보를 출력합니다
"""
response = requests.get(site_url)
print(f' *** {site_url} returned {response.status_code} after {response.elapsed} seconds')


# 단일 스레드로 응답을 가져오는 예제
if __name__ == '__main__':
for site_url in tutorialpoints_url:
visit_site(site_url)
print(f" *** end of the program ***")


*** https://www.tutorialspoint.com/python/index.htm returned 200 after 0:00:00.091103 seconds
*** https://www.tutorialspoint.com/cplusplus/index.htm returned 200 after 0:00:00.069889 seconds
*** https://www.tutorialspoint.com/java/index.htm returned 200 after 0:00:00.075864 seconds
*** https://www.tutorialspoint.com/html/index.htm returned 200 after 0:00:00.075270 seconds
*** https://www.tutorialspoint.com/cprogramming/index.htm returned 200 after 0:00:00.077984 seconds
*** end of the program ***

출력 결과에서 무엇을 관찰하셨나요? URL들이 순차적으로(sequentially) 처리되고 있습니다. 만약 전 세계 여러 지역에 분산된 수백 개의 URL을 방문해야 한다면, 프로그램은 서버의 응답을 기다리느라 상당한 시간을 낭비하게 될 것입니다.

이제 요청을 병렬로(parallel) 전송하고 응답을 기다리지 않고 다음 작업으로 진행하는 멀티 스레드 프로그램을 작성해 보겠습니다.

from threading import Thread

# 전달된 URL에 요청을 보내고 상태 코드를 반환하는 함수
def visit_site(site_url):
"""
웹사이트 URL에 GET 요청을 보내고 응답 정보를 출력합니다
"""
response = requests.get(site_url)
print(f' *** {site_url} returned {response.status_code} after {response.elapsed} seconds')

# URL 목록을 순회하며 각 URL마다 스레드 생성
if __name__ == '__main__':
for site_url in tutorialpoints_url:
t = Thread(target=visit_site, args=(site_url,))
t.start()


*** https://www.tutorialspoint.com/python/index.htm returned 200 after 0:00:00.082176 seconds
*** https://www.tutorialspoint.com/html/index.htm returned 200 after 0:00:00.086269 seconds
*** https://www.tutorialspoint.com/java/index.htm returned 200 after 0:00:00.100746 seconds
*** https://www.tutorialspoint.com/cplusplus/index.htm returned 200 after 0:00:00.120744 seconds *** https://www.tutorialspoint.com/cprogramming/index.htm returned 200 after 0:00:00.111489 seconds

출력 결과를 보면 이번에는 URL이 목록 순서대로 처리되지 않았습니다. 각 요청이 독립적인 스레드에서 동시에 실행되었기 때문입니다. 이것이 바로 동시성이 주는 강력한 이점입니다.

핵심 정리

  • threading 라이브러리를 사용하면 모든 Python 호출 가능 객체(callable)를 자체 스레드에서 실행할 수 있습니다.

  • start() 메서드는 site_url 인자를 전달하여 visit_site 함수를 호출합니다.

  • 스레드는 한 번 시작되면 자신만의 실행 흐름에서 동작하며, 그 실행은 운영체제(OS)가 완전히 관리합니다.

추가로, 생성한 스레드가 아직 실행 중인지 아니면 종료되었는지 확인하고 싶다면 is_alive() 함수를 사용할 수 있습니다.

if t.is_alive():
print(f' *** {t} is Still executing')
else:
print(f' *** {t} is Completed')


*** <Thread(Thread-10, stopped 4820)> is Completed

이처럼 Python의 스레드를 활용하면 네트워크 요청과 같은 I/O 바운드(I/O-bound) 작업을 효율적으로 병렬 처리할 수 있어, 전체 프로그램의 실행 시간을 크게 단축할 수 있습니다.