이 자습서에서는 Python의 내장 스레딩 모듈을 사용하여 Python의 다중 스레딩 기능을 탐색하는 방법을 배웁니다.
프로세스 및 스레드의 기본부터 시작하여 동시성 및 병렬 처리의 개념을 이해하면서 Python에서 멀티스레딩이 작동하는 방식을 배우게 됩니다. 그런 다음 내장 스레딩 모듈을 사용하여 Python에서 하나 이상의 스레드를 시작하고 실행하는 방법을 배웁니다.
시작하자.
목차
프로세스 대 스레드: 차이점은 무엇입니까?
프로세스란?
프로세스는 실행해야 하는 프로그램의 모든 인스턴스입니다.
Python 스크립트 또는 Chrome과 같은 웹 브라우저에서 화상 회의 애플리케이션에 이르기까지 무엇이든 될 수 있습니다. 컴퓨터에서 작업 관리자를 시작하고 성능 -> CPU로 이동하면 현재 CPU 코어에서 실행 중인 프로세스와 스레드를 볼 수 있습니다.
프로세스 및 스레드 이해
내부적으로 프로세스에는 해당 프로세스에 해당하는 코드와 데이터를 저장하는 전용 메모리가 있습니다.
프로세스는 하나 이상의 스레드로 구성됩니다. 스레드는 운영 체제가 실행할 수 있는 가장 작은 명령 시퀀스이며 실행 흐름을 나타냅니다.
각 스레드에는 자체 스택과 레지스터가 있지만 전용 메모리는 없습니다. 프로세스와 연결된 모든 스레드는 데이터에 액세스할 수 있습니다. 따라서 데이터와 메모리는 프로세스의 모든 스레드에서 공유됩니다.
N개의 코어가 있는 CPU에서 N개의 프로세스는 동일한 시간에 병렬로 실행할 수 있습니다. 그러나 동일한 프로세스의 두 스레드는 병렬로 실행할 수 없지만 동시에 실행할 수 있습니다. 다음 섹션에서 동시성 대 병렬성의 개념을 다룰 것입니다.
지금까지 배운 내용을 바탕으로 프로세스와 스레드의 차이점을 요약해 보겠습니다.
FeatureProcessThreadMemoryDedicated memoryShared memoryMode of executionParallel, concurrentConcurrent; 그러나 운영 체제CPython 인터프리터에서 처리하는 parallelExecution은 아닙니다.
파이썬의 멀티스레딩
Python에서 GIL(Global Interpreter Lock)은 한 스레드만 잠금을 획득하고 특정 시점에서 실행할 수 있도록 합니다. 모든 스레드가 실행하려면 이 잠금을 획득해야 합니다. 이렇게 하면 특정 시점에서 단일 스레드만 실행될 수 있고 동시 멀티스레딩이 방지됩니다.
예를 들어, 동일한 프로세스의 두 스레드, t1 및 t2를 고려하십시오. 스레드는 t1이 특정 값 k를 읽을 때 동일한 데이터를 공유하기 때문에 t2는 동일한 값 k를 수정할 수 있습니다. 이는 교착 상태 및 바람직하지 않은 결과를 초래할 수 있습니다. 그러나 스레드 중 하나만 잠금을 획득하고 모든 인스턴스에서 실행할 수 있습니다. 따라서 GIL은 스레드 안전성도 보장합니다.
그렇다면 파이썬에서 멀티스레딩 기능을 어떻게 달성할 수 있을까요? 이를 이해하기 위해 동시성과 병렬성의 개념에 대해 논의해 보겠습니다.
동시성 대 병렬성: 개요
하나 이상의 코어가 있는 CPU를 고려하십시오. 아래 그림에서 CPU에는 4개의 코어가 있습니다. 이는 주어진 순간에 4개의 다른 작업을 병렬로 실행할 수 있음을 의미합니다.
4개의 프로세스가 있는 경우 각 프로세스는 4개의 코어 각각에서 독립적으로 동시에 실행할 수 있습니다. 각 프로세스에 두 개의 스레드가 있다고 가정해 보겠습니다.
스레딩이 작동하는 방식을 이해하기 위해 멀티코어에서 단일 코어 프로세서 아키텍처로 전환해 보겠습니다. 언급했듯이 특정 실행 인스턴스에서 단일 스레드만 활성화될 수 있습니다. 그러나 프로세서 코어는 스레드 간에 전환할 수 있습니다.
예를 들어, I/O 바운드 스레드는 종종 사용자 입력 읽기, 데이터베이스 읽기 및 파일 작업과 같은 I/O 작업을 기다립니다. 이 대기 시간 동안 다른 스레드가 실행될 수 있도록 잠금을 해제할 수 있습니다. 대기 시간은 n초 동안 잠자기와 같은 간단한 작업일 수도 있습니다.
요약: 대기 작업 중에 스레드는 잠금을 해제하여 프로세서 코어가 다른 스레드로 전환할 수 있도록 합니다. 대기 기간이 완료된 후 이전 스레드가 실행을 재개합니다. 프로세서 코어가 스레드 간에 동시에 전환하는 이 프로세스는 멀티스레딩을 용이하게 합니다. ✅
애플리케이션에서 프로세스 수준 병렬 처리를 구현하려면 대신 다중 처리를 사용하는 것이 좋습니다.
Python 스레딩 모듈: 첫 번째 단계
Python은 Python 스크립트로 가져올 수 있는 스레딩 모듈과 함께 제공됩니다.
import threading
파이썬에서 스레드 객체를 생성하기 위해 스레드 생성자를 사용할 수 있습니다: threading.Thread(…). 이것은 대부분의 스레딩 구현에 충분한 일반 구문입니다.
threading.Thread(target=...,args=...)
여기,
- target은 Python 호출 가능을 나타내는 키워드 인수입니다.
- args는 대상이 받는 인수의 튜플입니다.
이 자습서의 코드 예제를 실행하려면 Python 3.x가 필요합니다. 코드를 다운로드하고 따라해보세요.
Python에서 스레드를 정의하고 실행하는 방법
대상 함수를 실행하는 스레드를 정의해 보겠습니다.
대상 함수는 some_func입니다.
import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() print(threading.active_count())
위의 코드 조각이 하는 일을 구문 분석해 보겠습니다.
- 스레딩 및 시간 모듈을 가져옵니다.
- 함수 some_func에는 설명적인 print() 문이 있으며 2초 동안 휴면 작업을 포함합니다. time.sleep(n)은 함수를 n초 동안 휴면 상태로 만듭니다.
- 다음으로 target이 some_func인 스레드 thread_1을 정의합니다. threading.Thread(target=…)는 스레드 객체를 생성합니다.
- 참고: 함수 호출이 아닌 함수 이름을 지정하십시오. some_func()가 아닌 some_func를 사용하십시오.
- 스레드 개체를 생성하면 스레드가 시작되지 않습니다. 스레드 개체에서 start() 메서드를 호출하면 됩니다.
- 활성 스레드 수를 얻으려면 active_count() 함수를 사용합니다.
Python 스크립트는 메인 스레드에서 실행 중이며, 출력에서 볼 수 있듯이 활성 스레드 수가 2가 되도록 some_func 함수를 실행하기 위해 다른 스레드(thread1)를 생성합니다.
# Output Running some_func... 2 Finished running some_func.
출력을 자세히 살펴보면 thread1을 시작할 때 첫 번째 print 문이 실행된다는 것을 알 수 있습니다. 그러나 슬립 작업 동안 프로세서는 메인 스레드로 전환하고 스레드 1이 실행을 마칠 때까지 기다리지 않고 활성 스레드 수를 출력합니다.
스레드가 실행을 마칠 때까지 기다리기
thread1이 실행을 끝내도록 하려면 스레드를 시작한 후 join() 메서드를 호출하면 됩니다. 그렇게 하면 메인 스레드로 전환하지 않고 thread1이 실행을 마칠 때까지 기다립니다.
import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() thread1.join() print(threading.active_count())
이제 활성 스레드 수를 인쇄하기 전에 thread1의 실행이 완료되었습니다. 따라서 메인 스레드만 실행 중이며, 이는 활성 스레드 수가 1개임을 의미합니다. ✅
# Output Running some_func... Finished running some_func. 1
Python에서 여러 스레드를 실행하는 방법
다음으로 두 개의 다른 기능을 실행하는 두 개의 스레드를 만들어 보겠습니다.
여기서 count_down은 숫자를 인수로 받아 그 숫자에서 0까지 카운트다운하는 함수입니다.
def count_down(n): for i in range(n,-1,-1): print(i)
우리는 0에서 주어진 숫자까지 세는 또 다른 Python 함수인 count_up을 정의합니다.
def count_up(n): for i in range(n+1): print(i)
📑 range() 함수를 range(start, stop, step) 구문으로 사용할 경우 기본적으로 종점 stop은 제외됩니다.
– 특정 숫자에서 0으로 카운트다운하려면 음수 단계 값 -1을 사용하고 정지 값을 -1로 설정하여 0이 포함되도록 할 수 있습니다.
– 마찬가지로 n까지 세려면 stop 값을 n + 1로 설정해야 합니다. start와 step의 기본값은 각각 0과 1이므로 range(n + 1)을 사용하여 시퀀스 0을 얻을 수 있습니다. n을 통해
다음으로, count_down 및 count_up 함수를 각각 실행하기 위해 두 개의 스레드(thread1 및 thread2)를 정의합니다. 두 함수 모두에 대해 인쇄 문과 절전 작업을 추가합니다.
스레드 객체를 생성할 때 대상 함수에 대한 인수는 args 매개변수에 대한 튜플로 지정되어야 합니다. 두 함수(count_down 및 count_up)가 하나의 인수를 취하기 때문입니다. 값 뒤에 명시적으로 쉼표를 삽입해야 합니다. 이렇게 하면 후속 요소가 None으로 유추되기 때문에 인수가 여전히 튜플로 전달됩니다.
import threading import time def count_down(n): for i in range(n,-1,-1): print("Running thread1....") print(i) time.sleep(1) def count_up(n): for i in range(n+1): print("Running thread2...") 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까지 카운트다운합니다.
# Output Running thread1.... 10 Running thread2... 0 Running thread1.... 9 Running thread2... 1 Running thread1.... 8 Running thread2... 2 Running thread1.... 7 Running thread2... 3 Running thread1.... 6 Running thread2... 4 Running thread1.... 5 Running thread2... 5 Running thread1.... 4 Running thread1.... 3 Running thread1.... 2 Running thread1.... 1 Running thread1.... 0
스레드 1과 스레드 2는 모두 대기 작업(수면)을 포함하므로 번갈아 실행되는 것을 볼 수 있습니다. count_up 함수가 5까지 카운트를 마치면 thread2는 더 이상 활성화되지 않습니다. 따라서 우리는 thread1에만 해당하는 출력을 얻습니다.
합산
이 자습서에서는 Python의 내장 스레딩 모듈을 사용하여 멀티스레딩을 구현하는 방법을 배웠습니다. 주요 내용을 요약하면 다음과 같습니다.
- Thread 생성자는 스레드 개체를 만드는 데 사용할 수 있습니다. threading.Thread(target=
,args=( ))를 사용하면 args에 지정된 인수로 대상 호출 가능을 실행하는 스레드가 생성됩니다. - Python 프로그램은 기본 스레드에서 실행되므로 생성한 스레드 개체는 추가 스레드입니다. active_count() 함수를 호출하면 모든 인스턴스에서 활성 스레드 수를 반환합니다.
- 스레드 개체에서 start() 메서드를 사용하여 스레드를 시작하고 join() 메서드를 사용하여 실행이 완료될 때까지 기다릴 수 있습니다.
대기 시간을 조정하고 다른 I/O 작업을 시도하는 등의 방법으로 추가 예제를 코딩할 수 있습니다. 향후 Python 프로젝트에서 멀티스레딩을 구현해야 합니다. 즐거운 코딩!🎉