파이썬 멀티 스레드와 병렬 처리
파이썬의 멀티 스레드 동작 방식과 병렬 처리 방법에 대한 포스팅입니다.
- 📌 개요
- 📌 프로세스(Process)와 스레드(Thread)
- 📌 멀티 스레드란 무엇인가
- 📌 언어별 스레드 비교와 파이썬 GIL
- 📌 파이썬 스레드의 활용
- 📌 파이썬 병렬 처리
- 📌 결론
- 📌 내용 추가
- 📌 References
📌 개요
학부 고급 프로그래밍 수업에서 Python과 관련한 주제로 발표를 하게 되었고, Multi-Thread의 동작 방식과 병렬처리 방법에 대한 학습 내용을 기록한다.
📌 프로세스(Process)와 스레드(Thread)
핵심 내용 설명에 앞서 프로세스와 스레드를 이해해야 한다.
프로세스란 실행 중인 프로그램이다.
- OS로부터 CPU, Memory 등의 자원을 할당받고 관리된다.
- 고유한 Process ID를 갖는다. (작업 관리자의 세부 정보 탭에서 프로세스 및 ID 확인이 가능하다.)

스레드란 실행 흐름의 단위이다.
프로세스에서 할당받은 자원을 이용한다.프로세스내에 존재하는 단위이며 하나면싱글 스레드, 다수면멀티 스레드이다.
📌 멀티 스레드란 무엇인가
예시로, 3개 파일을 다운로드 하려고 한다.
싱글 스레드의 경우, 한 번에 하나의 파일만을 순차적으로 다운로드해야 한다. 멀티 스레드의 경우, 여러 파일을 동시에 각각의 스레드에서 다운로드할 수 있다.
이를 각각 순차 실행(Sequential)과 병렬 실행(Concurrent)이라고 한다.

위와 같은 동작이 일반적으로 생각하는 멀티 스레드이다. 하지만 결론부터 얘기하면 Python의 스레드에서는 위와 같은 동작이 불가하다.
📌 언어별 스레드 비교와 파이썬 GIL
Python에서 멀티 스레드는 가능하지만 병렬처리는 불가하다. 어떤 의미인지 아래 예제를 통해 확인해 보자.
0~100000000까지 합 출력 코드이다. 싱글 스레드는 0~100000000까지 하나의 스레드에서 계산을 수행한다. 멀티 스레드는 50000000씩 두 개의 스레드에서 계산을 수행한다.
P.S. 정확한 수치를 원한다면 CPU 성능을 낮추어 자원에 자체적인 부하와 제약을 주고 시험할 수 있다. 제어판 → 하드웨어 및 소리 → 전원 옵션 → 전원 관리 옵션 설정 편집 → 고급 전원 관리 옵션 설정 변경 
✨ Python 싱글 스레드
실행 시간: 4,500 ms
import time
from threading import Thread
def work(id, start, end, result):
total = 0
for i in range(start, end):
total += i
result.append(total)
return
if __name__ == "__main__":
start = time.time()
result = list()
th1 = Thread(target=work, args=(1, 0, 100000000, result))
th1.start()
th1.join()
print(f"Result: { sum(result) }")
print(f"Time: { time.time() - start }")
✨ Python 멀티 스레드
실행 시간: 4,500 ms
import time
from threading import Thread
def work(id, start, end, result):
total = 0
for i in range(start, end):
print(i)
total += i
result.append(total)
return
if __name__ == "__main__":
start = time.time()
result = list()
th1 = Thread(target=work, args=(1, 0, 50000000, result))
th2 = Thread(target=work, args=(2, 50000000, 100000000, result))
th1.start()
th2.start()
th1.join()
th2.join()
print(f"Result: { sum(result) }")
print(f"Time: { time.time() - start }")
print(len(result))
✨ Java 싱글 스레드
실행 시간: 4,600 ms
import java.util.ArrayList;
public class Main {
public static void main(String[] args) throws Exception {
new Main();
}
public Main() throws Exception {
long startTime = System.currentTimeMillis();
TestThread th = new TestThread(0, 100000000);
th.start();
th.join();
long time = System.currentTimeMillis() - startTime;
System.out.println((double) time / 1000);
}
class TestThread extends Thread {
private ArrayList<Integer> list = new ArrayList<Integer>();
private int start;
private int end;
public TestThread(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public void run() {
long sum = 0;
for (int i = start; i < end; i++) {
list.add(i);
}
for(int d : list) sum += d;
System.out.println("sum: " + sum);
}
}
}
✨ Java 멀티 스레드
실행 시간: 3,500 ms
import java.util.ArrayList;
public class Main {
public static void main(String[] args) throws Exception {
new Main();
}
public Main() throws Exception {
long startTime = System.currentTimeMillis();
TestThread th1 = new TestThread(0, 50000000);
TestThread th2 = new TestThread(50000000, 100000000);
th1.start();
th2.start();
th1.join();
th2.join();
// while(th.isAlive()) {}
long time = System.currentTimeMillis() - startTime;
System.out.println((double) time / 1000);
}
class TestThread extends Thread {
private ArrayList<Integer> list = new ArrayList<Integer>();
private int start;
private int end;
public TestThread(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public void run() {
long sum = 0;
for (int i = start; i < end; i++) {
list.add(i);
}
for(int d : list) sum += d;
System.out.println("sum: " + sum);
}
}
}
✨ 내용 정리
Java의 경우 멀티 스레드가 싱글 스레드 보다 확연히 빠르다. Python의 경우 멀티 스레드와 싱글 스레드의 수행 속도가 거의 동일하다.
언어별로 실행 시간에 차이가 발생하는 것은 당연하다. 하지만, Python은 멀티 스레드와 싱글 스레드의 속도가 동일하다는 것을 통해 병렬 실행이 아닌 순차 실행되고 있다는 것을 증명할 수 있다.
해당 방식으로 동작하는 이유는 무엇일까?
위키에 따르면
Python에는GIL(Global Interpreter Lock)이라는 녀석이 자원 관리를 목적으로 인터프리터가 반드시 하나의스레드만을 수행하도록 제한해 놓았다고 한다.
(GIL은 기회가 된다면 별도 게시물에서 다룰 예정)

📌 파이썬 스레드의 활용
앞선 내용으로 Python의 스레드를 이해했다. 그렇다면 병렬처리가 불가한 스레드를 어떻게 활용할 수 있을까?
I/O 작업이 많을 경우이다.
Python은 I/O 작업이 발생 시, 스레드의 실행을 중지하고 해당 작업 종료까지 대기 상태로 빠진다.
즉, 멀티 스레드에서 사용 중인 스레드가 대기 상태로 빠지면 다른 스레드의 작업을 수행하게 되는 것이다.

디스크가 파일을 읽고 쓰거나 네트워크 카드(NIC)가 송/수신할 때와 같이, CPU가 아닌 하드웨어에서 발생되는 연산을 효율적으로 처리할 수 있다.
Web, REST API 서버, 대용량 로그 생성 등의 개발 시 활용 가능하다.
📌 파이썬 병렬 처리
Python 스레드로는 병렬처리가 불가하다는 사실을 알게 되었다. 그렇다면 Python에서 병렬처리 구현이 필요한다면 어떻게 해야 할까
멀티 프로세스를 사용하면 된다. multiprocessing 모듈을 사용하여 하나의 프로그램에 다수 프로세스를 생성/실행할 수 있다.
아래 예제 코드를 실행해 보면 실행 시간이 단축되는 것을 알 수 있고, 멀티 프로세스의 경우 실행 동안 작업 관리자에서 PID가 다중으로 생성되는 것을 확인할 수 있다.
✨ Python 싱글 프로세스
실행 시간: 4,500 ms
import time
from multiprocessing import Process, Queue
def work(id, start, end, result):
total = 0
for i in range(start, end):
total += i
result.put(total)
return
if __name__ == "__main__":
start = time.time()
START, END = 0, 100000000
result = Queue()
th1 = Process(target=work, args=(1, START, END, result))
th1.start()
th1.join()
result.put('STOP')
total = 0
while True:
tmp = result.get()
if tmp == 'STOP':
break
else:
total += tmp
print(f"Result: {total}")
print(f"Time: { time.time() - start }")
✨ Python 멀티 프로세스
실행 시간: 3,800 ms
import time
from multiprocessing import Process, Queue
def work(id, start, end, result):
total = 0
for i in range(start, end):
total += i
result.put(total)
return
if __name__ == "__main__":
start = time.time()
START, END = 0, 100000000
result = Queue()
th1 = Process(target=work, args=(1, START, END//2, result))
th2 = Process(target=work, args=(2, END//2, END, result))
th1.start()
th2.start()
th1.join()
th2.join()
result.put('STOP')
total = 0
while True:
tmp = result.get()
if tmp == 'STOP':
break
else:
total += tmp
print(f"Result: {total}")
print(f"Time: { time.time() - start }")
📌 결론
Python에서 병렬처리는 멀티 프로세스로 구현이 가능하다. 멀티 스레드는 결국 하나의 스레드를 수행하므로 병렬처리가 되지 않는다.
📌 내용 추가
(2024-07-12) 내용 추가: GIL 사용을 선택 사항으로 변경 예정
