파이썬에는 여러가지 종류가 있다
종류에 따라 스레드를 사용하는 방법이 다 다르며, 우리는 CPython을 기준으로 사용
GIL(Global Interpreter Lock)
- python에서 자원의 무결성과 동기화를 위한 기능
- 스레드는 동시성을 고도화 하기 위한 개념이며, 프로세스의 자원을 공유한다
- 그러므로 자원의 무결성과 동기화를 위한 로직이 필요
- 그러므로 자원의 무결성과 동기화를 위한 로직이 필요
1. 기본 개념
- CPython에서 여러 thread를 사용하는 경우 하나의 thread만이 자원에 접근하도록 제한하는 mutex
- mutex: mutual exclusion(상호배제)
- CPython의 메모리 관리 방식이 thread-safe하지 않기 때문에 GIL을 사용
- coarse-grained lock 방식
coarse-grained lock
- 큰 단위로 lock을 잡는 방식
- 단일코어 CPU에서 사용
- 락이 빈번하면 문맥교환이나 시간이 더 든다
find-grained
- 작은 단위로 락을 잡는 방식
- 멀티코어 CPU에서 사용
- 여러 thread에서 lock을 걸 수 있다
2. 특징
- coarse-grained lock 방식
- 단일코어 CPU에서 성능을 발휘할 수 있다.
Thread 구현
1. Python에서 thread를 구현하는 방법
저수준 라이브러리
- Thread pool이나 lock을 커스터마이징 할 수 있다
- thread원시 기능을 변경해서 사용 가능
- python2에서는
thread를 python3에서는_thread사용
고수준 라이브러리
- 일반적으로 사용하며, 간편하게 사용 가능
2. 고수준 라이브러리 threading
함수 구현
import threading
def worker(count):
# thread의 이름과 인자를 출력
print("name: %s, argument: %s" %(threading.currentThread().getName(), count))
def main():
for i in range(5):
# target: thread로 실행할 함수의 이름
# args: 함수의 인자를 tuple로 전달
# name: thread의 이름
t = threading.Thread(target=worker, name="thread %i" %i, args=(i,))
t.start()
if __name__ == '__main__':
main()
threading.Thread클래스에 thread로 처리할 함수를 넣고 초기화- 초기화 할 때 쓰레드의 이름과 매개변수를 설정할 수 있다
결과

- 스레드로 실행하면 비순차적으로 작업이 실행되어 실행 결과가 달라질 수 있다.
- CPU의 갯수가 1개이거나 코어가 1개라면 순차 실행이 된다
클래스 메서드로 구현
import threading
# threading.Thread 클래스를 상속
class Worker(threading.Thread):
# 생성자 함수
# Thread 클래스의 생성자를 호출
def __init__(self, args, name=""):
threading.Thread.__init__(self)
self.args = args
# 실제 실행할 함수(run)를 오버라이딩 하여 구현
def run(self):
# thread의 이름과 클래스의 속성을 출력
print("name: %s, argument: %s" %(self.name, self.args[0]))
def main():
for i in range(5):
t = Worker(name="thread %i" %(i), args=(i, ))
# 클래스 내부의 오버라이딩한 run을 실행
t.start()
if __name__ == '__main__':
main()
threading.Thread클래스를 상속받아run()함수를 오버라이딩 하여 구현threading.Thread의 생성자를 호출하지 않으면 모듈이 초기화 되지 않아 오류 발생start()를 호출하면 내부의run()메서드 호출
결과

3. logging
- python2에서는 thread에서 print사용시 안전하지 않고 출력도 불규칙하다
- print문을 실행할 때 thread에 대해 별다른 조치 없이 실행
- 안전한 logging 모듈 사용이 필요
thread에서 안전한 logging 구현
import logging
import threading
# logging 모듈 설정
logging.basicConfig(level=logging.DEBUG, format="name: %(threadName)s argument: %(message)s")
def worker(count):
logging.debug(count)
def main():
for i in range(5):
t = threading.Thread(target=worker, name="thread %i" %i, args=(i,))
t.start()
if __name__ == '__main__':
main()
4. Daemon Thread
- 쓰레드를 데몬으로 사용할 수 있다.
- 백그라운드에서 실행할 스레드를 데몬으로 띄워서 동작
- 스레드에 접근해서 조작을 할 수 없다.
그렇다면 왜 데몬으로 사용?
- 그냥 스레드를 띄우면, 해당 스레드가 종료될 때 까지 기다린다.
- 즉, 메인프로그램이 종료되지 않음
- 정기적이고, 부수적인 작업을 데몬 스레드로 띄운다.
- 메인 프로그램 종료시 같이 종료
데몬 스레딩 예시
import time
import logging
import threading
logging.basicConfig(level=logging.DEBUG, format="(%(threadName)s) %(message)s")
def daemon():
logging.debug("start")
time.sleep(5)
logging.debug("Exit")
def main():
t = threading.Thread(name="daemon", target=daemon)
# 스레드를 데몬으로 설정
t.setDaemon(True)
t.start()
if __name__ == "__main__":
main()
결과
- 예상한 결과를 5초 뒤에 Exit 또한 같이 띄우는 것인데 start만 출력하고 종료되었다.
- 메인 프로그램이 종료되며 스레드도 기다리지 않고 같이 종료
데몬 스레드를 기다리게 하는 방법
t.setDaemon(True)
t.start()
# join을 추가
t.join()
- 스레드의
.join()은 해당 스레드가 끝날 때 까지 기다리는 것을 의미한다.
결과