파이썬 스레딩: 소개 – koreantech.org
본 튜토리얼에서는 파이썬 내장 스레딩 모듈을 활용하여 파이썬의 다중 스레딩 기능을 살펴보는 방법을 안내합니다.
우리는 프로세스와 스레드의 기초를 다루면서, 동시성과 병렬 처리 개념을 이해하고, 파이썬에서 멀티스레딩이 어떻게 동작하는지 알아볼 것입니다. 그런 다음, 내장 스레딩 모듈을 사용하여 파이썬에서 하나 이상의 스레드를 시작하고 실행하는 구체적인 방법을 배울 것입니다.
자, 시작해볼까요?
프로세스와 스레드: 핵심 차이점은 무엇일까요?
프로세스란 무엇일까요?
프로세스란 실행 중인 프로그램의 모든 인스턴스를 의미합니다.
파이썬 스크립트, 웹 브라우저(예: Chrome), 화상 회의 애플리케이션 등 다양한 형태가 될 수 있습니다. 컴퓨터에서 작업 관리자를 실행하고 성능 -> CPU 탭으로 이동하면 현재 CPU 코어에서 실행 중인 프로세스와 스레드의 목록을 확인할 수 있습니다.
프로세스와 스레드 이해
내부적으로 각 프로세스는 해당 프로세스에 속하는 코드와 데이터를 저장하기 위한 전용 메모리 공간을 가집니다.
프로세스는 하나 이상의 스레드로 구성됩니다. 스레드는 운영 체제가 실행할 수 있는 가장 작은 단위의 명령 시퀀스이며, 실행 흐름을 나타냅니다.
각 스레드는 고유한 스택과 레지스터를 가지고 있지만, 전용 메모리는 없습니다. 특정 프로세스에 속한 모든 스레드는 해당 프로세스의 데이터에 접근할 수 있습니다. 즉, 데이터와 메모리는 프로세스의 모든 스레드 간에 공유됩니다.

N개의 코어를 가진 CPU에서 N개의 프로세스는 동시에 병렬로 실행될 수 있습니다. 그러나 동일한 프로세스에 속한 두 개의 스레드는 병렬로 실행될 수는 없지만, 동시에 실행될 수는 있습니다. 동시성과 병렬성의 개념은 다음 섹션에서 자세히 다룰 것입니다.
지금까지 학습한 내용을 바탕으로 프로세스와 스레드의 차이점을 간략하게 요약해 보겠습니다.
| 특징 | 프로세스 | 스레드 |
| 메모리 | 전용 메모리 | 공유 메모리 |
| 실행 방식 | 병렬, 동시 | 동시 실행; 하지만 CPython 인터프리터에서는 진정한 병렬 실행은 아님 |
파이썬의 멀티스레딩
파이썬에서 GIL(Global Interpreter Lock)은 단 하나의 스레드만이 특정 시점에 잠금을 획득하고 실행될 수 있도록 제한합니다. 모든 스레드는 실행을 위해 이 잠금을 획득해야 합니다. 이러한 메커니즘은 특정 시점에 단일 스레드만 실행되도록 보장하여 동시 멀티스레딩을 방지합니다.
예를 들어, 동일한 프로세스에 속한 두 스레드 t1과 t2를 생각해 봅시다. 두 스레드가 동일한 데이터를 공유하므로 t1이 특정 값 k를 읽을 때 t2는 동일한 값 k를 수정할 수 있습니다. 이는 교착 상태 및 예측 불가능한 결과를 초래할 수 있습니다. 그러나 GIL은 한 번에 하나의 스레드만 잠금을 획득하고 실행되도록 보장하므로 스레드 안전성도 확보합니다.
그렇다면 파이썬에서 멀티스레딩 기능을 어떻게 활용할 수 있을까요? 이를 이해하기 위해 동시성과 병렬성의 개념을 살펴보겠습니다.
동시성 대 병렬성: 개요
하나 이상의 코어를 가진 CPU를 생각해 봅시다. 아래 그림에서 CPU는 4개의 코어를 가지고 있습니다. 이는 특정 시점에 4개의 서로 다른 작업을 병렬로 실행할 수 있음을 의미합니다.
만약 4개의 프로세스가 있다면, 각 프로세스는 4개의 코어에서 각각 독립적으로 동시에 실행될 수 있습니다. 각 프로세스에 두 개의 스레드가 있다고 가정해 봅시다.

스레딩의 동작 방식을 더 잘 이해하기 위해 멀티코어에서 단일 코어 프로세서 아키텍처로 시선을 돌려보겠습니다. 앞서 언급했듯이, 특정 실행 시점에 하나의 스레드만 활성화될 수 있습니다. 그러나 프로세서 코어는 스레드 간을 빠르게 전환할 수 있습니다.

예를 들어, I/O 바운드 스레드는 사용자 입력 대기, 데이터베이스 읽기, 파일 작업 등의 I/O 작업을 기다리는 경우가 많습니다. 이 대기 시간 동안, 다른 스레드가 실행될 수 있도록 잠금을 해제할 수 있습니다. 대기 시간은 단순히 n초 동안 잠을 자는 것과 같은 간단한 작업일 수도 있습니다.
요약: 대기 작업 중에는 스레드가 잠금을 해제하여 프로세서 코어가 다른 스레드로 전환할 수 있도록 합니다. 대기 시간이 완료된 후에는 이전 스레드가 다시 실행을 시작합니다. 프로세서 코어가 스레드 간을 동시에 전환하는 이 과정을 통해 멀티스레딩이 구현됩니다. ✅
애플리케이션에서 프로세스 수준의 병렬 처리를 구현해야 한다면, 멀티프로세싱을 사용하는 것이 더 좋습니다.
파이썬 스레딩 모듈: 첫걸음
파이썬은 파이썬 스크립트로 import 할 수 있는 내장 스레딩 모듈을 제공합니다.
import threading
파이썬에서 스레드 객체를 생성하려면 `threading.Thread(...)` 생성자를 사용할 수 있습니다. 이는 대부분의 스레딩 구현에 적합한 일반적인 구문입니다.
threading.Thread(target=..., args=...)
여기서,
- `target`은 파이썬 호출 가능 객체를 나타내는 키워드 인수입니다.
- `args`는 대상 함수가 전달받을 인수의 튜플입니다.
본 튜토리얼의 코드 예제를 실행하려면 파이썬 3.x 이상 버전이 필요합니다. 코드를 다운로드하고 직접 따라해 보세요.
파이썬에서 스레드를 정의하고 실행하는 방법
대상 함수를 실행하는 스레드를 정의해 보겠습니다.
대상 함수는 `some_func` 입니다.
import threading
import time
def some_func():
print("some_func 실행 중...")
time.sleep(2)
print("some_func 실행 완료.")
thread1 = threading.Thread(target=some_func)
thread1.start()
print(threading.active_count())
위 코드 조각의 동작을 분석해 보겠습니다.
- threading과 time 모듈을 import 합니다.
- `some_func` 함수는 설명적인 print() 문을 포함하고, 2초 동안 대기하는 작업을 포함합니다. `time.sleep(n)`은 함수를 n초 동안 일시 중단시킵니다.
- 다음으로, `target`이 `some_func`인 스레드 `thread1`을 정의합니다. `threading.Thread(target=...)`는 스레드 객체를 생성합니다.
- 주의: 함수 호출이 아니라 함수 이름을 지정해야 합니다. `some_func()`가 아니라 `some_func`를 사용해야 합니다.
- 스레드 객체를 생성한다고 해서 스레드가 자동으로 시작되는 것은 아닙니다. 스레드 객체에서 `start()` 메서드를 호출해야 합니다.
- 현재 활성 상태인 스레드 수를 확인하려면 `active_count()` 함수를 사용합니다.
파이썬 스크립트는 기본(main) 스레드에서 실행되며, 출력 결과에서 확인할 수 있듯이, `some_func` 함수를 실행하기 위한 또 다른 스레드(thread1)를 생성하므로 활성 스레드 수는 2가 됩니다.
# 출력 some_func 실행 중... 2 some_func 실행 완료.
출력 결과를 자세히 살펴보면, `thread1`을 시작할 때 첫 번째 print 문이 실행되는 것을 알 수 있습니다. 그러나 sleep 작업 동안 프로세서는 메인 스레드로 전환하고, 스레드 1이 실행을 완료할 때까지 기다리지 않고 활성 스레드 수를 출력합니다.

스레드 실행 완료 대기하기
`thread1`이 실행을 완료하도록 하려면 스레드를 시작한 후에 `join()` 메서드를 호출하면 됩니다. 이렇게 하면 메인 스레드로 전환하지 않고 `thread1`이 실행을 완료할 때까지 기다립니다.
import threading
import time
def some_func():
print("some_func 실행 중...")
time.sleep(2)
print("some_func 실행 완료.")
thread1 = threading.Thread(target=some_func)
thread1.start()
thread1.join()
print(threading.active_count())
이제 활성 스레드 수를 출력하기 전에 `thread1`의 실행이 완료되었습니다. 따라서 메인 스레드만 실행 중이므로 활성 스레드 수는 1이 됩니다. ✅
# 출력 some_func 실행 중... some_func 실행 완료. 1
파이썬에서 여러 스레드 실행하기
다음으로, 두 개의 다른 함수를 실행하는 두 개의 스레드를 만들어 보겠습니다.
여기서 `count_down` 함수는 숫자를 인수로 받아 해당 숫자부터 0까지 카운트다운하는 함수입니다.
def count_down(n):
for i in range(n,-1,-1):
print(i)
다음으로, 0부터 주어진 숫자까지 세는 또 다른 파이썬 함수인 `count_up` 함수를 정의합니다.
def count_up(n):
for i in range(n+1):
print(i)
📑 `range()` 함수를 `range(start, stop, step)` 구문으로 사용할 때, 기본적으로 종료 값 `stop`은 포함되지 않습니다.
– 특정 숫자에서 0까지 카운트다운하려면 음수 단계 값 `-1`을 사용하고, 0이 포함되도록 종료 값을 `-1`로 설정할 수 있습니다.
– 마찬가지로 `n`까지 세려면 종료 값을 `n+1`로 설정해야 합니다. `start`와 `step`의 기본값은 각각 0과 1이므로 `range(n+1)`을 사용하여 0부터 n까지의 시퀀스를 얻을 수 있습니다.
다음으로, `count_down` 및 `count_up` 함수를 각각 실행하기 위해 두 개의 스레드(`thread1`과 `thread2`)를 정의합니다. 두 함수 모두에 print 문과 sleep 작업을 추가합니다.
스레드 객체를 생성할 때 대상 함수에 대한 인수는 `args` 매개변수에 튜플 형태로 지정해야 합니다. 두 함수(`count_down`과 `count_up`) 모두 하나의 인수를 취하므로, 값 뒤에 명시적으로 쉼표를 삽입해야 합니다. 이렇게 하면 후속 요소가 `None`으로 추론되는 것을 방지하고, 인수가 튜플로 전달되도록 할 수 있습니다.
import threading
import time
def count_down(n):
for i in range(n,-1,-1):
print("스레드1 실행 중....")
print(i)
time.sleep(1)
def count_up(n):
for i in range(n+1):
print("스레드2 실행 중...")
print(i)
time.sleep(1)
thread1 = threading.Thread(target=count_down,args=(10,))
thread2 = threading.Thread(target=count_up,args=(5,))
thread1.start()
thread2.start()
출력 결과를 보면:
- `count_up` 함수는 `thread2`에서 실행되며, 0부터 시작하여 최대 5까지 셉니다.
- `count_down` 함수는 `thread1`에서 실행되며, 10부터 0까지 카운트다운합니다.
# 출력 스레드1 실행 중.... 10 스레드2 실행 중... 0 스레드1 실행 중.... 9 스레드2 실행 중... 1 스레드1 실행 중.... 8 스레드2 실행 중... 2 스레드1 실행 중.... 7 스레드2 실행 중... 3 스레드1 실행 중.... 6 스레드2 실행 중... 4 스레드1 실행 중.... 5 스레드2 실행 중... 5 스레드1 실행 중.... 4 스레드1 실행 중.... 3 스레드1 실행 중.... 2 스레드1 실행 중.... 1 스레드1 실행 중.... 0
스레드 1과 스레드 2는 모두 대기 작업(sleep)을 포함하므로, 번갈아 가면서 실행되는 것을 볼 수 있습니다. `count_up` 함수가 5까지 카운트를 완료하면 `thread2`는 더 이상 활성화되지 않습니다. 따라서 `thread1`에 해당하는 출력만 나타납니다.
요약
본 튜토리얼에서는 파이썬의 내장 스레딩 모듈을 사용하여 멀티스레딩을 구현하는 방법을 배웠습니다. 주요 내용을 요약하면 다음과 같습니다.
- 스레드 생성자는 스레드 객체를 생성하는 데 사용할 수 있습니다. `threading.Thread(target=<호출 가능 객체>, args=(<인수 튜플>))`를 사용하면 `args`에 지정된 인수를 사용하여 대상 호출 가능 객체를 실행하는 스레드가 생성됩니다.
- 파이썬 프로그램은 기본 스레드에서 실행되므로, 생성한 스레드 객체는 추가 스레드를 생성합니다. `active_count()` 함수를 호출하면 현재 활성 상태인 스레드 수를 반환합니다.
- 스레드 객체에서 `start()` 메서드를 사용하여 스레드를 시작하고, `join()` 메서드를 사용하여 스레드 실행이 완료될 때까지 기다릴 수 있습니다.
대기 시간을 조정하거나 다른 I/O 작업을 시도하는 등 다양한 방법으로 추가적인 예제를 코딩할 수 있습니다. 앞으로 파이썬 프로젝트에서 멀티스레딩을 적극적으로 활용해 보세요! 즐거운 코딩되세요!🎉