[Python] 멀티프로세싱으로 처리 속도 올리기 (multiprocessing)
왜 내 챗봇은 헛소리를 할까? RAG와 Reranker로 파헤친 정확도의 비밀 (문서 순서의 중요성)
왜 내 챗봇은 헛소리를 할까? RAG와 Reranker로 파헤친 정확도의 비밀 (문서 순서의 중요성)
👋 소개안녕하세요! 대학생 개발자 주이어입니다!저는 최근에 저희 학교를 대상으로한 전용 AI 챗봇을 제작하고 있었는데요.그러다보니 자연스럽게 RAG와 파인튜닝과 같은 AI 관련 기술들을 많
blog.juyear.dev
이전 글 읽으러 가기!
👋 소개
안녕하세요! 대학생 개발자 주이어입니다!
오늘은 제가 예전에 외주를 받으면서 배웠던 멀티프로세싱 개념을 최근에 또 사용하게 돼서
이 참에 한 번 정리해보려고 이렇게 글을 쓰게 되었습니다.
먼저 멀티쓰레딩과 멀티프로세싱의 차이점에 대해서 간단하게 정리한 후,
python에서 어떻게 멀티프로세싱(Process, Lock, Manager 등)을 사용할 수 있는지에 대해서 소개해드리겠습니다.
❓ 멀티쓰레딩 VS 멀티프로세싱
멀티쓰레딩과 멀티프로세싱의 차이점에 대해서 알기 위해서는 먼저 쓰레드와 프로세스의 개념에 대해서 알아야 합니다.
쓰레드란?
쓰레드는 프로세스의 하위 개념으로 이해하면 쉬운데, 프로세스 내에서 실제로 작업을 수행하는 단위입니다.
즉, 프로세스는 하나 이상의 쓰레드가 모여 이루어지며, 이 쓰레드들은 프로세스의 메모리 공간을 공유합니다.
프로세스란?
프로세스는 운영체제에서 실행 중인 하나의 프로그램을 의미합니다. 각각의 프로세스는 독립적인 메모리 공간을 가지고 있으며,
다른 프로세스와는 기본적으로 메모리를 공유하지 않습니다.
멀티쓰레딩이란?
말 그대로 하나의 프로세스 내에서 여러 개의 쓰레드를 동시에 사용해 작업을 병렬적으로 처리하는 방식을 말합니다.
쓰레드들이 동일한 메모리 공간을(하나의 프로세스 안에 있으므로) 공유하므로, 자원 접근이 빠르지만 동시에 공유 자원에 대한 충돌을 방지하기 위한 동기화가 필요합니다.
(공유 자원 충돌이란, 2개의 쓰레드가 동시에 변수의 값을 수정했을 때 실제로는 값이 변하지 않거나 원하는 값이 나오지 않는 오류를 의미합니다.)
멀티프로세싱이란?
멀티프로세싱은 여러 개의 프로세스를 동시에 사용해 작업을 병렬적으로 처리하는 방식입니다.
각각의 프로세스는 독립적인 메모리 공간을 가지기 때문에, 자원 충돌 문제는 줄어들지만 프로세스 간의 통신이 필요해지고, 메모리 사용량도 상대적으로 많아집니다.
이 때 대충 생각해보면, 프로세스가 메모리도 더 많고 더 큰 개념이니 멀티프로세싱으로 작업하는 것이 더 좋은거 아닌가 라고 생각하실 수 있습니다.
하지만 이러한 멀티프로세싱은 그만큼 메모리도 더 많이 차지하고, 다루기에도 멀티쓰레딩보다 더 복잡합니다.
그래서 멀티쓰레딩은 간단한 작업을 동시에 처리할때 주로 사용되며,
멀티프로세싱은 무거운 작업을 동시에 처리할때 사용됩니다.
❓ 어떻게 사용할까?
from multiprocessing import Manager, Process, Value, Lock
python은 multiprocessing을 내장 라이브러리로 지원하기 때문에 별도의 설치 없이 위와 같이 바로 사용하실 수 있습니다.
그럼 이제 각각의 기능에 대해서 정리해보도록 하겠습니다.
Manager
with Manager() as manager:
collected_urls = manager.dict()
post_index = Value('i', post_index_start)
index_lock = Lock()
exist_urls = load_collected_urls()
Manager는 프로세스 간의 공유 자원을 설정하거나 실제 프로세스를 생성하여 작업을 진행하는 등의 멀티프로세싱의 기본 컨트롤러 역할을 해줍니다. 멀티프로세싱과 관련된 코드는 보통 Manager 구문 안에서 실행됩니다.
위 코드는 manager.dict()로 공유 가능한 딕셔너리를 생성하고, Value()로 공유 변수, Lock()으로 자원 충돌 방지를 위한 락을 생성하는 코드입니다.
Process
num_workers = 4
pages_per_worker = total_pages // num_workers
processes = []
for i in range(num_workers):
print("준비중")
page_start = start_page + i * pages_per_worker
if i == num_workers - 1:
page_end = end_page
else:
page_end = page_start + pages_per_worker - 1
page_range = range(page_start, page_end + 1)
p = Process(target=worker, args=(page_range, collected_urls, post_index, index_lock))
p.start()
processes.append(p)
for p in processes:
p.join()
위 코드에서는 num_workers로 프로세스 개수를 설정해주고,
각각의 프로세스에 작업 범위와 필요한 값들을 전달한 뒤
실제로 프로세스를 실행시키는 코드입니다.
Process는 새로운 프로세스를 생성해주는 객체이며, target(작업을 할 함수), args(해당 함수에 넘겨줄 인자)을 인수로 받습니다.
생성된 인스턴스는 p.start()와 같이 실행시킬 수 있으며, p.join()으로 작업이 끝날 때까지 기다립니다.
Value
post_index = Value('i', post_index_start)
Value는 위와 같이 공유 변수를 생성할 때 사용됩니다.
일반 변수를 쓸 경우, 각각의 프로세스가 자기만의 값(메모리를 공유하지 않기 때문)을 가지기 때문에, 값을 공유하거나 동시에 다루는 경우에 충돌이 생길 수 있습니다. 이러한 문제점을 해결하기 위해 Value를 사용하여 변수를 생성해줍니다.
Lock
index_lock = Lock()
Lock은 위와 같이 생성할 수 있으며,
프로세스의 병렬 작업을 잠깐동안 멈추는(기다리는) 역할을 합니다.
Lock 역시 공유 자원의 충돌을 막기 위해서 사용되며, 특정 프로세스가 중요한 작업을 처리하는 동안 다른 프로세스를 기다리게 함으로써 충돌을 막아주는 역할을 합니다.
def get_next_post_index(post_index, index_lock):
with index_lock:
val = post_index.value
post_index.value += 1
return val
위와 같이 순서가 겹치면 안되는 post_index의 값을 조정할 때, Lock을 사용하여 다른 프로세스가 똑같은 index 값을 사용하지 않도록 할 수 있습니다.
이외에도 Queue, Pool, Event 등 다른 구성 요소도 많이 있지만 자주 사용되는 개념만 정리해보았습니다.
😊 마무리
지금까지 멀티쓰레딩과 멀티프로세싱의 차이점 그리고 멀티프로세싱의 사용 방법에 대해서 정리해보았습니다.
처음엔 단순히 "속도 향상"을 위한 기술 정도로만 생각했었는데, 직접 프로젝트에 적용하면서 각 방식의 차이점과 쓰임새 그리고 내부 작동 원리까지 깊이 이해할 수 있었습니다.
이러한 기술은 단순히 코드를 실행하는 걸 넘어 시스템적인 사고도 중요하다는 걸 느꼈습니다.
지금까지 읽어주셔서 감사드리며, 다음에는 더 재미있고 유익한 정보로 찾아오도록 하겠습니다.
저와 같이 이런 분야를 공부하고 프로젝트를 제작하고 싶다면 아래 링크로 들어와주세요!
KYT CODING COMMUNITY Discord 서버에 가입하세요!
Discord에서 KYT CODING COMMUNITY 커뮤니티를 확인하세요. 21명과 어울리며 무료 음성 및 텍스트 채팅을 즐기세요.
discord.com
KYT CODING COMMUNITY 가입하기!