공작소굴

여러가지 일들을 동시에 처리하는 사람을 일컬어 '멀티 플레이어'라고 합니다. 우리가 만드는 프로그램도 여러가지 일을 동시에 처리하도록 할 수 있습니다. 바로 쓰레드(Thread)를 이용하는 것입니다. 이번 포스팅에서는 쓰레드가 무엇인지, 파이썬에서 쓰레드를 어떻게 사용하는 지 알아보겠습니다.

 

1. 쓰레드(Thread)

 

 쓰레드(Thread)란 하나의 목표를 가진 프로그램을 수행(Process)할 때, 독립적으로 운영되는 작업을 말합니다.

 

비유하자면...

 캠핑카를 타고 떠나는 여행을 생각해 볼까요?

 

 캠핑카를 타고 여행가기라는 목적(Project)을 달성하기 위해서는 안전하게 목적지에 도착하는 것이 목표인 프로세스(Process)를 수행해야겠죠? 이 때, 중요하게 필요한 작업운전하기와 길찾기입니다.

 

 캠핑카를 운전하고 있는 나(Main thread)는 운전을 하면서 동시에 길도 찾아야 합니다. 운전을 하면서 길을 찾을 수 있는 방법들을 살펴볼까요?

 

1. 잠시 정차하여 지도를 보는 방법

 이 방법은 내가(Main thread) 스스로 길 찾기 작업을 수행하는 것입니다. 길 찾기가 특정 상황에서만 수행되는 급하지 않은 작업이라면 괜찮겠지만, 돌발상황이나 다른 길로 가고자 할 때에는 짜증도 나고 시간도 오래 걸립니다.

 

2. 길을 잘 아는 친구에게 전화를 걸어 물어 보는 방법

 이 방법은 나와 여행(Process 1)을 같이 가진 않지만 길을 잘 찾는 친구(Precess 2)가 길을 알려주는 것입니다. 이 방법은 어떨까요? 약간 답답하지 않나요?

 내가 어디를 가는지, 여기가 어딘지 알려줘야 하고, 필요한 정보 이외에 잡다한 이야기들을 하느라 시간이 지체될 수도 있겠죠. 만약 길을 잘 못 찾아 주는 친구라면? 다른 친구를 찾아봐야 할 지도 모르겠네요.

 

3. 내비게이션이 안내해 주는 길을 따라 가는 방법

 운전을 하는 나(Main thread)와 상관없이 동작하는 내비(Sub thread)는 내 여행길(Prcess 1)에 항상 함께하기 때문에 목적지가 어디인지, 현재 위치가 어디인지, 얼만큼 왔는지 등등의 정보(Data, Memory)를 나(Main thread)와 함께 공유합니다. 덕분에 2번 방법보다 빠르고 효율적입니다. 그리고 내(Main thread)가 운전을 하는 동안에도 내비(Sub thread)는 별도로 동작하므로 1번 방법처럼 운전을 멈추고 길을 찾을 필요도 없습니다.

 

1. 잠시 정차하여 지도를 볼 수도 있고 (Main thread 안에서 순차적 수행)

2. 길을 잘 아는 친구에게 전화를 걸어 물어볼 수도 있지만 (다른 Process를 동작시켜 통신함)

3. 내비게이션이 안내해 주는 길을 따라 가는게 편합니다. (멀티쓰레드를 동작시킴)

 

 이처럼, 하나의 프로세스(여행) 내에서 독립적으로 동작(운전, 길찾기)하는 작업들쓰레드(Thread)라고 하며 여러개의 쓰레드가 동작하는 작업을 멀티 쓰레드라고 합니다.

 

2. 쓰레드 만들기

 

 이번에는 파이썬에서 쓰레드를 직접 생성해 보겠습니다.

 쓰레드를 만든다는 것은 마치 혼자서 회사를 운영하던 사장(Main thread = 이하 Main)이 직원(Sub thread = 이하 Thread)을 채용해 일을 나누는 것에 비유할 수 있습니다.

 

1) 일반 쓰레드 (Normal Thread)

 쓰레드를 만들 때 주요하게 쓰이는 구문은 아래와 같습니다.

 

1. 직원 채용 공고를 내고

import threading  # 쓰레드 라이브러리

 

2. 할 일을 알려주고, 필요한 기본 정보들을 넘겨 줍니다.

thread_1 threading.Thread(target = 쓰레드 동작 함수, arg = (필요한 인자값)) # 쓰레드 함수 연결

 

3. 직원이 일을 시작합니다.

thread_1.start() # 쓰레드 동작 시작

 

 사장(Main)은 직원(Thread)을 뽑았으니 일을 줄여 3개의 일을 0.1초마다 하고,

 직원(Thrad)은 5개의 일을 0.2초마다 하도록 만들고 동작을 시켜 보겠습니다.

 

# 쓰레드를 만들어 주는 라이브러리를 가져온다.
import threading 
import time 

# 쓰레드가 할 일 정의 (작업개수, 작업속도, 직원이름)
def doingJob(_jobs, _delay, _name):
    
    print(f"{_name} 님에게 {_jobs}개의 일이 주어졌습니다.\n")

    for i in range(_jobs):
        print(f"{_name}님이 {i+1}번 째 일을 완료하였습니다.")
        time.sleep(_delay)

    print(f"{_name}님이 일을 마치고 퇴근합니다.\n")

# 쓰레드 생성
thread_1 = threading.Thread(target = doingJob, args = (5, 0.2, '  일반직원'))
thread_1.start()

# 메인 쓰레드
doingJob(3, 0.1, '사장')

 

결과를 살펴보면, 사장(Main)은 일을 빠르게 끝내고 퇴근을 하지만(Main thread 종료) 일이 서툰 일반직원(Normal thread)은 일을 마칠 때 까지 야근을 하고 퇴근(Sub thread 종료)합니다. 물론 일이 빨리 끝나면, 사장보다 빨리 퇴근할 수도 있습니다.

 

 

2) 데몬 쓰레드 (Daemon thread)

 직원(Thread)이 늘어나고 사무실이 커지면서 사무실을 관리하는 직원(Thread)을 뽑았습니다.

 관리직원(Thread)은 회사의 주요 업무를 수행하지는 않지만, 유령(Daemon)처럼, 보이지않는 곳에서 직원들이 쾌적한 환경에서 업무를 할 수 있도록 청소를 하고(Garbage Collector) 서류를 저장하는(Auto Saving) 등의 역할을 합니다.

 이 관리직원(Daemon thread)은 비록 중요한 업무는 아니지만(Low priority) 조용히 자기 업무를 하다가 모든 직원들(Normal threads)이 퇴근을 하면 함께 퇴근 합니다.

 관리직원(Daemon thread)은 사장(Main)이 퇴근하더라도 한 명의 직원(thread)이라도 남아 있다면, 퇴근을 하지 않습니다.

 

 데몬 쓰레드를 만드는 방법은 일반 쓰레드와 같습니다. 다만 만들어진 쓰레드를 데몬으로 할 것인지를 설정해 주기만 하면 됩니다.

 

thread_2.daemon = True

  

 업무 개수 : 사장 (3개) / 일반직원 (5개) / 관리직원 (10개)

# 쓰레드를 만들어 주는 라이브러리를 가져온다.
import threading 
import time 

# 쓰레드가 할 일 정의 (작업개수, 작업속도, 직원이름)
def doingJob(_jobs, _delay, _name):
    
    print(f"{_name} 님에게 {_jobs}개의 일이 주어졌습니다.\n")

    for i in range(_jobs):
        print(f"{_name}님이 {i+1}번 째 일을 완료하였습니다.")
        time.sleep(_delay)

    print(f"{_name}님이 일을 마치고 퇴근합니다.\n")

# 쓰레드 생성
thread_1 = threading.Thread(target = doingJob, args = (5, 0.1, '  일반직원'))
thread_1.start()

# 데몬 쓰레드 생성
thread_2 = threading.Thread(target = doingJob, args = (10, 0.1, '    관리직원'))
thread_2.daemon = True
thread_2.start()

# 메인 쓰레드
doingJob(3, 0.1, '사장')

 

 결과를 살펴보면, 사장이 퇴근(Main 종료)을 했음에도 직원들은 남아서 일을 합니다. 일반직원의 야근이 끝나면 관리직원은 본인의 일이 아직 끝나지 않았음에도 강제퇴근을 합니다.

 

 

마무리

 

 이번 포스팅에서는 다양한 비유와 예시를 통해 쓰레드에 대해 알아보았습니다.

 

 저는 주로 아두이노와 같은 심플한 하드웨어 제어프로그램을 다루다보니 쓰레드 기능을 적용해서 사용해 본 적은 없습니다. 제가 아는 제어프로그램에서의 멀티태스킹은 타이머를 사용하여 두 개 이상의 프로세스를 왔다갔다하며 빠르게 진행하는 것입니다. 물론 거기에 따른 메모리 공유 문제를 해결하는 데에는 세마포어 비슷하게 토큰을 만들어 돌리기도 합니다.

 사실 싱글코어 CPU라면 멀티태스킹, 멀티쓰레딩의 개념은 위와 다르지 않습니다. 무지 빠른 CPU여러 쓰레드를 돌아가며 한번에 한 가지 작업을 순차적으로 처리하지만, 겉 보기에는 병렬처리가 되는 것 처럼 보이는 것이지요.

 

 요새는 멀티코어 CPU들이 대부분이므로 실제로 서로 다른 CPU가 병렬로 작업을 처리하면서 메모리도 공유하고 메시지도 주고 받으며 그야말로 멀티태스킹, 멀티쓰레딩 작업을 할 수 있게 되었습니다. 

 

 잘은 모르지만(몰라도 되지만), 우리가 파이썬이나 자바 등에서 멀티쓰레드 라이브러리를 사용하여 구현된 쓰레드들은 컴파일러들이 알아서 해당 PC의 CPU의 코어에 맞게 분배하고 동작시키지 않을까요?

 

 언젠가는 멀티쓰레드를 활용한 응용 프로그램을 만들어 보기를 꿈꾸며 포스팅을 마치도록 하겠습니다.

 감사합니다.

 

 끝.

공유하기

facebook twitter kakaoTalk kakaostory naver band