파이썬 코드로 보는 멀티스레드
부제: 현직자가 중요하다고 생각하는 기본기
![](https://github.com/KoEonYack/Tistory-Coveant/blob/master/Article/OperatingSystem/%EB%A9%80%ED%8B%B0%EC%93%B0%EB%A0%88%EB%93%9C_%ED%8C%8C%EC%9D%B4%EC%8D%AC/img/cover.png?raw=true)
인턴으로 일하던 시절 첫 과제로 C#으로 윈도우 애플리케이션을 만들었습니다. 멀티스레드 구현을 잘못하여서 프로그램 내에서 여러 작업을 동시에 하였더니 프로그램이 뻗은 경험이 있습니다. 멀티스레드는 코드몽키가 되는 것이 비전과 사명이 아닌 이상 꼭 알아야할 중요한 개념입니다. 현재 운영체제를 공부하고 있으신 학생분들도 이 부분을 잘 공부하시길 바라며, 그 중요한 멀티스레드를 파이썬 코드와 함께 살펴보도록 하곘습니다.
1. 쓰레드란?
![](https://github.com/KoEonYack/Tistory-Coveant/blob/master/Article/OperatingSystem/%EB%A9%80%ED%8B%B0%EC%93%B0%EB%A0%88%EB%93%9C_%ED%8C%8C%EC%9D%B4%EC%8D%AC/img/thvspss.png?raw=true)
꼬꼬마 텔레토비였던 3학년 1학기 학부생 시절을 생각해보면 운영체제에서 인터럽트까지 꾸역꾸역 공부했더니 반겨주는 실지렁이들. 어떻게 실지렁이가 쓰레드가 된다는 말인지, Context Swith로 전환되는 프로세스 안에 또 다른 무엇인가가 있다는 것이 너무나 이상했습니다. 운영체제 그림 중에서 제일 컴퓨터공학 스럽지 않은 그림이라고 생각했습니다.
![](https://github.com/KoEonYack/Tistory-Coveant/blob/master/Article/OperatingSystem/%EB%A9%80%ED%8B%B0%EC%93%B0%EB%A0%88%EB%93%9C_%ED%8C%8C%EC%9D%B4%EC%8D%AC/img/chrome.png?raw=true)
![](https://github.com/KoEonYack/Tistory-Coveant/blob/master/Article/OperatingSystem/%EB%A9%80%ED%8B%B0%EC%93%B0%EB%A0%88%EB%93%9C_%ED%8C%8C%EC%9D%B4%EC%8D%AC/img/ps.png?raw=true)
Chrome에서 Youtube에서 음악을 들으면서 웹 서핑을 해본다고 생각해봅니다. 그렇다면 작업 관리창(맥의 경우 활성 상태 보기)에 보이는 Chrome 프로세스안의 생성된 쓰레드가 Youtube를 실행하면서 다른 스레드가 Google 웹 창을 보여주는 것입니다. 즉 스레드를 기준으로 프로세스 내의 실행 흐름 단위 가 생성되게 됩니다.
스레드
- 프로세스 내에 실행 흐름 단위입니다.
- 쓰레드는 프로세스에 할당된 메모리, CPU 등의 자원을 사용합니다.
- Stack만 별도의 메모리를 할당하며 Code, Data, Heap은 쓰레드간 공유합니다.
- 한 스레드의 결과가 다른 스레드에 영향 끼칩니다. 크롬 하나의 탭에 문제가 생기면 없으면 크롬 자체를 다시 실행해야 하는 경우가 있습니다.
- 스레드의 경우 디버깅이 어렵기에 동기화 문제는 주의해서 구현해야합니다.
멀티 스레드
- 한 개의 단일 어플리케이션(응용프로그램)은 여러 스레드로 구성 후 작업 처리해야합니다.
- 한글에서 싱글 스레드를 사용한다면 프린트를 하는 경우 문서 수정은 불가능할 것입니다.
- 프로세스를 생성하는 것은 고비용입니다. 스레드를 사용한다면 시스템 자원 소모 감소 및 처리량 증가시킬 수 있습니다.
- 쓰레드는 이미 공유하고 있기에 프로세스를 사용했다면 생길 통신 부담이 감소합니다.
- 멀티 스레드를 사용할 경우 디버깅이 어렵습니다. 자원 공유 문제(일명 교착상태)가 생깁니다.
1-1. 파이썬 코드로 스레드 구현.
import logging
import threading
import time
# 스레드에서 실행할 함수
def work(name):
logging.info("[Sub-Thread] %s: 시작합니다.", name)
time.sleep(3) # 3초간 sleep합니다.
logging.info("[Sub-Thread] %s: 종료합니다.", name)
# 메인 영역
if __name__ == "__main__": # main thread 흐름을 타는 시작점
format = "%(asctime)s: %(message)s" # Logging format 설정
logging.basicConfig(format=format, level=logging.INFO, datefmt="%H:%M:%S")
x = threading.Thread(target=work, args=('A',))
logging.info("[Main-Thread] 쓰레드 시작 전")
x.start() # 서브 스레드 시작
logging.info("[Main-Thread] 프로그램을 종료합니다.")
logging.basicConfig
는 log 형식을 지정하기 위해서 정의한 코드입니다.- threading.Thread 란?
- Target: 스레드가 실행할 함수(일명 callable object)를 지정해줍니다. 여기서는 work 함수를 실행합니다.
- args: 함수에 넘길 인자입니다. work함수의 name에 인자 값으로 "A"를 줍니다.
.start()
호출할 경우 스레드 활동을 시작합니다.
1-2. 실행 결과
18:56:12: [Main-Thread] 쓰레드 시작 전
18:56:12: [Sub-Thread] A: 시작합니다.
18:56:12: [Main-Thread] 프로그램을 종료합니다.
18:56:15: [Sub-Thread] A: 종료합니다.
- work()함수에서 3초 쉬기에 메인 스레드가 종료되고 Sub-Thread가 종료됩니다.
1-3. 실행 결과 도식화
![](https://github.com/KoEonYack/Tistory-Coveant/blob/master/Article/OperatingSystem/%EB%A9%80%ED%8B%B0%EC%93%B0%EB%A0%88%EB%93%9C_%ED%8C%8C%EC%9D%B4%EC%8D%AC/img/1.png?raw=true)
- [1] a.start()가 실행되면 스레드 활동이 시작됩니다.
- [2] Sub Thread A는 sleep(3)이 실행되면 3초 실행을 멈춥니다.
- [3] Sub Thread가 멈춘 사이 Main Thread는 종료됩니다.
- [4] Sub Thread가 종료됩니다.
2. 쓰레드 + join()
Main이 종로되지 않고 실행하는 스레드가 종료 될 때까지 기다리게 하는 방법이 있습니다. 바로 join()
을 사용하는 것입니다. join() 메서드가 호출된 스레드가 종료될 때까지 호출하는 스레드를 블록 합니다. 즉 Main Thread에서 Sub Thread를 join한다면 Sub Thread가 종료될때까지 기다립니다.
2-1. 파이썬 코드로 구현
import logging
import threading
import time
# 스레드에서 실행할 함수
def thread_func(name):
logging.info("[Sub-Thread] %s: 시작합니다.", name)
time.sleep(3) # 3초간 sleep합니다.
logging.info("[Sub-Thread] %s: 종료합니다.", name)
# 메인 영역
if __name__ == "__main__": # main thread 흐름을 타는 시작점
format = "%(asctime)s: %(message)s" # Logging format 설정
logging.basicConfig(format=format, level=logging.INFO, datefmt="%H:%M:%S")
logging.info("[Main-Thread] 쓰레드 시작 전")
x = threading.Thread(target=thread_func, args=('A',))
logging.info("[Main-Thread] 쓰레드 시작 전")
a.start() # 서브 스레드 시작
logging.info("[Main-Thread] 쓰레드 종료를 기다립니다.")
a.join() # join() 추가!!!
logging.info("[Main-Thread] 프로그램을 종료합니다.")
2-2. 실행 결과
19:14:31: [Main-Thread] 쓰레드 시작 전
19:14:31: [Main-Thread] 쓰레드 시작 전
19:14:31: [Sub-Thread] A: 시작합니다.
19:14:31: [Main-Thread] 쓰레드 종료를 기다립니다.
19:14:34: [Sub-Thread] A: 종료합니다.
19:14:34: [Main-Thread] 프로그램을 종료합니다.
- Sub Thread가 종료된 다음 Main Thread가 종료되는 것을 볼 수 있습니다.
2-3. 실행 결과 도식화
![](https://github.com/KoEonYack/Tistory-Coveant/blob/master/Article/OperatingSystem/%EB%A9%80%ED%8B%B0%EC%93%B0%EB%A0%88%EB%93%9C_%ED%8C%8C%EC%9D%B4%EC%8D%AC/img/2.png?raw=true)
- [1] Main Thread에서 a.join()을 실행하면 Sub Thread가 끝날때까지 기다립니다.
- [2] Sub Thread가 끝나면 Main Thread가 실행됩니다.
3. 데몬스레드
3-1. 데몬쓰레드란?
데몬스레드는 카카오 첫 공채 블라인드 2차 필기 시험에 나온 주제입니다. 지금까지 Sub Thread는 Main Thread가 종료되더라도 끝까지 실행되거나 심지어 Main Thread가 Sub Thread가 종료되기를 기다리게 할 수 있었습니다. 그러나 데몬쓰레드는 Main Thread가 종료되면 즉시 종료되는 스레드입니다.
- 백그라운드에서 실행하는 스레드입니다.
- 메인스레드 종료시 즉시 종료됩니다.
- 워드에서 자동저장 기능을 데몬 스레드로 만들 수 있습니다. 워드를 종료하면 자동저장 데몬 스레드는 바로 소멸됩니다.
- 데몬 스레드가 아닌 일반 스레드는 작업 종료시 까지 실행됩니다.
3-2. 파이썬 코드로 구현
import logging
import threading
import time
# 스레드 실행 함수
def work(name, d):
logging.info("[Sub-Thread] %s: 시작합니다.", name)
for i in d:
print(i)
logging.info("[Sub-Thread %s: 종료합니다.", name)
# 메인 영역
if __name__ == "__main__":
# Logging format 설정
format = "%(asctime)s: %(message)s"
logging.basicConfig(format=format, level=logging.INFO, datefmt="%H:%M:%S")
# daemon을 True로 줍니다.
x = threading.Thread(target=work, args=('A', range(100)), daemon=True)
y = threading.Thread(target=work, args=('B', range(100)), daemon=True)
logging.info("[Main-Thread] 쓰레드 실행 전")
# 서브 스레드 시작
x.start()
y.start()
logging.info("[Main-Thread] 프로그램을 종료합니다.")
- threading.Thread에
daemon=True
를 주면 데몬스레드로 생성됩니다. x.isDaemon()
이 True라면 데몬스레드입니다.- 데몬스로드로 생성을 하지 않았더라도
x.daemon = True
한다면 데몬스레드로 만들 수 있습니다.
3-3. 실행 결과
19:24:56: [Main-Thread] 쓰레드 실행 전
19:24:56: [Sub-Thread] A: 시작합니다.
A:0
19:24:56: [Sub-Thread] B: 시작합니다.
B:0
A:1
B:1
19:24:56: [Main-Thread] 프로그램을 종료합니다.
3-4. 실행 결과 도식화
![](https://github.com/KoEonYack/Tistory-Coveant/blob/master/Article/OperatingSystem/%EB%A9%80%ED%8B%B0%EC%93%B0%EB%A0%88%EB%93%9C_%ED%8C%8C%EC%9D%B4%EC%8D%AC/img/3.png?raw=true)
- work함수의 인자로
range(100)
이 들어갔기에 데몬스레드가 아니라면 1부터 100까지 출력되야 합니다. - 그러나 Main Thread가 종료된다면 데몬 스레드가 종료되기에 A 데몬 스레드는 1까지만 출력, B는 0까지만 출력하고 종료되었습니다.
- 결과는 코드를 실행하는 운영체제, CPU 환경에 따라서 조금씩 다르게 나올 수 있습니다.
Ref.
- python docs threading — 스레드 기반 병렬 처리
- 인프런. 동시성과 병렬성 문법 배우기
'Computer Science > Computer Architecture' 카테고리의 다른 글
파이썬 코드로 보는 Lock, DeadLock, Race Condition (13) | 2021.04.17 |
---|---|
👀 한 눈에 확인하는 컴퓨터 구조 (2) | 2019.05.02 |
CISC / RISC (0) | 2018.07.14 |
[컴퓨터 구조]Chapter 3. Arithmetic for Computers (2) | 2017.10.25 |
[컴퓨터 구조]Chapter 1. Computer Abstractions and Technology (7) | 2017.10.24 |