| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- 구현
- 다익스트라
- 코딩테스트
- AWS
- BFS
- 알고리즘
- GROUPBY
- OrderBy
- Baekjoon
- EC2
- MST
- 자바
- 최단경로
- SQL
- union find
- 그래프 탐색
- 프림
- 피로그래밍
- Database
- SQL코딩테스트
- db
- 백준
- 프로그래머스
- 배포
- Java
- 누적합
- JOIN
- 크루스칼
- Pirogramming
- django
- Today
- Total
NullNull
Python 쓰레드 실행하기 본문
Python 쓰레드 관련 코드를 작성하다가 예상치 못한 상황을 맞이했다.
우선 코드부터 보자
import threading
import time
import random
def sub_task(idx, timeout):
print("subtask started " + str(idx))
time.sleep(timeout)
print("subtask finished " + str(idx))
def main_thread():
print("main thread started")
for i in range(10):
thread = threading.Thread(target = sub_task, args = (i, random.randint(1, 10)))
thread.start()
print("main thread finished")
if __name__ == "__main__":
main_thread()
원래 내 의도는 메인 쓰레드가 자식 쓰레드의 실행이 끝나면 종료되는 것이었다. 이 코드의 실행 결과는 다음과 같다.
main thread started
subtask started 0
subtask started 1
subtask started 2
subtask started 3
subtask started 4
subtask started 5
subtask started 6
subtask started 7
subtask started 8
subtask started 9
main thread finished
subtask finished 1
subtask finished 2
subtask finished 0
subtask finished 9
subtask finished 6
subtask finished 3
subtask finished 7
subtask finished 5
subtask finished 8
subtask finished 4
아니 왜 자꾸 먼저 죽어… 메인 쓰레드야…
이 문제를 해결하고자 구글의 힘을 빌렸다.
threading.Thread.join()
import threading
import time
import random
def sub_task(idx, timeout):
print("subtask started " + str(idx))
#time.sleep(timeout)
print("subtask finished " + str(idx))
def main_thread():
print("main thread started")
threads = []
for i in range(10):
thread = threading.Thread(target = sub_task, args = (i, random.randint(1, 10)))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
print("main thread finished")
if __name__ == "__main__":
main_thread()
가장 빠르게 해결한 방법은 thread모듈의 Thread.join()을 활용하는 것이었다.
thread들을 모아둘 자료구조를 하나 만들고 실행된 쓰레드를 하나씩 append 한다. 이 경우에 실행하면 아래와 같은 결과를 얻는다.
main thread started
subtask started 0
subtask started 1
subtask started 2
subtask started 3
subtask started 4
subtask started 5
subtask started 6
subtask started 8
subtask started 7
subtask started 9
subtask finished 4
subtask finished 9
subtask finished 3
subtask finished 8
subtask finished 5
subtask finished 1
subtask finished 2
subtask finished 0
subtask finished 6
subtask finished 7
main thread finished
오, 그래도 뭔가 의도한 대로 나오는 것 같다.
concurrent.futures.ThreadPoolExecutor.submit()
import concurrent.futures
import time
import random
def sub_task(idx, timeout):
print("subtask started " + str(idx))
time.sleep(timeout)
print("subtask finished " + str(idx))
def main_thread():
print("main thread started")
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as exe:
for i in range(10):
exe.submit(sub_task, i, random.randint(1, 10))
print("main thread finished")
if __name__ == "__main__":
main_thread()
아래와 같이 다른 모듈을 사용해보기도 했다.
concurrent.future 에 있는 ThreadPoolExecutor.submit() 을 활용한 코드이다.
결과는 다음과 같다.
main thread started
subtask started 0
subtask started 1
subtask started 2
subtask started 3
subtask started 4
subtask started 5
subtask started 6
subtask started 7
subtask started 8
subtask started 9
subtask finished 1
subtask finished 9
subtask finished 3
subtask finished 5
subtask finished 0
subtask finished 7
subtask finished 2
subtask finished 8
subtask finished 4
subtask finished 6
main thread finished
이것도 의도한 대로 동작한다.
그래서 둘이 뭐가 다를까?
일단 의도한 대로 코드를 동작시키는 것은 성공했다. 둘의 차이가 궁금해서 우리의 친구 GPT에게 문의했다.
1. threading.Thread.join():
- 단일 쓰레드 대기: threading.Thread.join()은 단일 쓰레드를 대기시키는 데 사용됩니다. 특정 쓰레드가 종료될 때까지 현재 쓰레드를 차단합니다.
- 저수준 API: threading.Thread은 저수준 쓰레드 관리를 위한 API를 제공합니다. 개발자는 쓰레드의 생성, 실행, 조율, 종료 등을 직접 제어할 수 있습니다.
2. concurrent.futures 모듈:
- 고수준 API: concurrent.futures 모듈은 고수준의 추상화를 제공하여 병렬 작업을 보다 쉽게 수행할 수 있습니다. 이 모듈을 사용하면 멀티쓰레딩 및 멀티프로세싱 작업을 추상화된 방식으로 다룰 수 있습니다.
- Executor 패턴: ThreadPoolExecutor 및 ProcessPoolExecutor는 Executor 패턴을 따르며, 작업을 실행하는 데 필요한 쓰레드나 프로세스를 관리합니다. 이는 작업의 스케일링 및 리소스 관리를 용이하게 합니다.
- 결과 처리 및 예외 처리: submit(), map() 등의 메서드를 통해 작업을 제출하고 실행할 수 있으며, Future 객체를 반환합니다. Future 객체를 통해 작업의 결과를 처리하고 예외를 관리할 수 있습니다.
장단점:
- threading.Thread.join():
- 장점: 저수준의 쓰레드 관리를 제공하여 쓰레드 동작을 더 세밀하게 제어할 수 있습니다.
- 단점: 다수의 쓰레드를 관리할 때는 코드가 복잡해질 수 있고, 쓰레드 간 동기화 및 조율에 관한 문제를 개발자가 직접 다뤄야 합니다.
- concurrent.futures 모듈:
- 장점: 고수준의 API를 제공하여 병렬 작업을 보다 쉽게 수행할 수 있습니다. Executor 패턴을 통해 작업 스케일링 및 리소스 관리가 용이합니다.
- 단점: 멀티플랫폼 환경에서는 멀티프로세싱을 위해 추가적인 리소스를 사용할 수 있습니다. 또한 일부 특정한 상황에서는 쓰레딩보다 더 느릴 수 있습니다.
따라서, threading.Thread.join()은 쓰레드 간의 작업을 더 세밀하게 조율할 때 사용되고, concurrent.futures 모듈은 고수준의 추상화를 통해 병렬 작업을 편리하게 처리할 때 사용됩니다. 선택은 작업의 성격과 요구사항에 따라 달라집니다.
결론은 threading.Thread.join()의 경우, 저수준의 관리를 제공하기 때문에 코드를 작성할 때 신경 써야할 부분들이 많아진다. 그러나 그로 인해서 더 세밀한 유즈케이스를 커버할 수 있어보인다.
반면에 concurrent.future 모듈은 고수준의 관리를 직접 제공하기 때문에, 코드 작성 시에 더 간편하게 코드를 작성할 수 있다. 그러나 자원이 한정되었다면 더 느리게 동작할 수 있다고 한다.
요구사항을 분석해보고 적당한 라이브러리를 정해서 써야겠다 ㅎ