스레드 풀

컴퓨터 프로그래밍에서 스레드 풀(Thread pool)은 컴퓨터 프로그램에서 병행 실행을 달성하기 위한 소프트웨어 디자인 패턴이다. 종종 복제 작업자 또는 작업자 그룹 모델이라고도 불리는[1] 스레드 풀은 감독 프로그램에 의해 병행 실행을 위해 할당될 태스크를 기다리는 여러 스레드를 유지한다. 스레드 풀을 유지함으로써 이 모델은 성능을 향상시키고 짧은 수명 태스크를 위해 스레드를 자주 생성하고 파괴하는 데 따른 실행 지연을 방지한다.[2] 또 다른 장점은 사용 가능한 스레드보다 적은 스레드를 사용할 때 시스템 부하를 제한하는 기능이다. 사용 가능한 스레드 수는 실행 완료 후 병렬 태스크 큐와 같이 프로그램에 사용 가능한 컴퓨팅 리소스에 맞춰 조정된다.
성능
[편집]스레드 풀의 크기는 태스크 실행을 위해 예비로 유지되는 스레드 수이다. 일반적으로 애플리케이션의 튜닝 가능한 매개변수이며, 프로그램 성능을 최적화하기 위해 조정된다.[3] 최적의 스레드 풀 크기를 결정하는 것은 성능 최적화에 중요하다.
각 태스크에 대해 새 스레드를 생성하는 것보다 스레드 풀의 한 가지 이점은 스레드 생성 및 파괴 오버헤드가 풀의 초기 생성으로 제한되어 더 나은 성능과 더 나은 시스템 안정성을 가져올 수 있다는 것이다. 스레드 및 관련 리소스를 생성하고 파괴하는 것은 시간 측면에서 비용이 많이 드는 프로세스일 수 있다. 그러나 예비 스레드 수가 과도하면 메모리가 낭비되고, 실행 가능한 스레드 간의 컨텍스트 스위칭은 성능 저하를 초래한다. 많은 CPU 사이클이 소요될 수 있는 다른 네트워크 호스트와의 소켓 연결은 두 개 이상의 네트워크 트랜잭션 과정에서 유지되는 스레드와 연결하여 더 효율적으로 유지할 수 있다.
스레드 시작 시간을 제쳐두고서라도 스레드 풀을 사용하는 것이 유용할 수 있다. 수동으로 스레드를 관리할 때보다 훨씬 쉽게 작업을 큐에 넣고, 병행성을 제어하며, 스레드를 더 높은 수준에서 동기화할 수 있는 스레드 풀 구현이 있다.[4][5] 이 경우 사용의 성능 이점은 부차적일 수 있다.
일반적으로 스레드 풀은 단일 컴퓨터에서 실행된다. 그러나 스레드 풀은 전체 처리량을 늘리기 위해 스레드 풀 자체일 수 있는 마스터 프로세스가 다른 컴퓨터의 작업자 프로세스에 작업을 분배하는 서버 팜과 개념적으로 관련이 있다. 처치 곤란 병렬 문제는 이 접근 방식에 매우 적합하다.
스레드 수는 대기 중인 태스크 수에 따라 애플리케이션 수명 동안 동적으로 조정될 수 있다. 예를 들어, 웹 서버는 많은 웹 페이지 요청이 들어오면 스레드를 추가할 수 있고, 요청이 줄어들면 스레드를 제거할 수 있다. 더 큰 스레드 풀을 갖는 비용은 리소스 사용량 증가이다. 스레드를 생성하거나 파괴할 시기를 결정하는 데 사용되는 알고리즘은 전체 성능에 영향을 미친다.
- 너무 많은 스레드를 생성하면 리소스가 낭비되고 사용되지 않는 스레드를 생성하는 데 시간이 소요된다.
- 너무 많은 스레드를 파괴하면 나중에 다시 생성할 때 더 많은 시간이 필요하다.
- 너무 느리게 스레드를 생성하면 클라이언트 성능이 저하될 수 있다(긴 대기 시간).
- 너무 느리게 스레드를 파괴하면 다른 프로세스의 리소스가 부족해질 수 있다.
언어별 구현
[편집]배시에서는 예를 들어 Xargs의 --max-procs / -P로 구현된다.
# Fetch 5 URLs in parallel
urls=(
"https://example.com/file1.txt"
"https://example.com/file2.txt"
"https://example.com/file3.txt"
"https://example.com/file4.txt"
"https://example.com/file5.txt"
)
printf '%s\n' "${urls[@]}" | xargs -P 5 -I {} curl -sI {} | grep -i "content-length:"
Go에서는 워커 풀이라고 불린다.
package main
import (
"fmt"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("worker", id, "started job", j)
time.Sleep(time.Second)
fmt.Println("worker", id, "finished job", j)
results <- j * 2
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= numJobs; a++ {
<-results
}
}
다음과 같이 출력된다.
$ time go run worker-pools.go
worker 1 started job 1
worker 2 started job 2
worker 3 started job 3
worker 1 finished job 1
worker 1 started job 4
worker 2 finished job 2
worker 2 started job 5
worker 3 finished job 3
worker 1 finished job 4
worker 2 finished job 5
real 0m2.358s
같이 보기
[편집]각주
[편집]- ↑ Garg, Rajat P. & Sharapov, Ilya Techniques for Optimizing Applications - High Performance Computing Prentice-Hall 2002, p. 394
- ↑ Holub, Allen (2000). 《Taming Java Threads》. Apress. 209쪽.
- ↑ Yibei Ling; Tracy Mullen; Xiaola Lin (April 2000). 《Analysis of optimal thread pool size》. 《ACM SIGOPS Operating Systems Review》 34. 42–55쪽. doi:10.1145/346152.346320. S2CID 14048829.
- ↑ “QThreadPool Class | Qt Core 5.13.1”.
- ↑ “GitHub - vit-vit/CTPL: Modern and efficient C++ Thread Pool Library.”. 《깃허브》. 2019년 9월 24일.
- ↑ Shved, Paul (2010년 1월 7일). “Easy parallelization with Bash in Linux” (영어). 《coldattic.info》. 2025년 1월 26일에 확인함.
- ↑ “xargs(1) - Linux manual page”. 《www.man7.org》. 2025년 1월 26일에 확인함.
- ↑ “Controlling Parallelism (GNU Findutils 4.10.0)”. 《www.gnu.org》. 2025년 1월 26일에 확인함.
- ↑ “Go by Example: Worker Pools”. 《gobyexample.com》. 2021년 7월 27일에 확인함.
- ↑ “Effective Go - The Go Programming Language”. 《golang.org》. 2021년 7월 27일에 확인함.
another approach that manages resources well is to start a fixed number of handle goroutines all reading from the request channel. The number of goroutines limits the number of simultaneous calls to process
- ↑ “The Case For A Go Worker Pool — brandur.org”. 《brandur.org》. 2021년 7월 27일에 확인함.
Worker pools are a model in which a fixed number of m workers (implemented in Go with goroutines) work their way through n tasks in a work queue (implemented in Go with a channel). Work stays in a queue until a worker finishes up its current task and pulls a new one off.
외부 링크
[편집]- "Query by Slice, Parallel Execute, and Join: A Thread Pool Pattern in Java" by Binildas C. A.
- "Thread pools and work queues" by Brian Goetz
- "A Method of Worker Thread Pooling" by Pradeep Kumar Sahu
- "Work Queue" by Uri Twig: C++ code demonstration of pooled threads executing a work queue.
- "Windows Thread Pooling and Execution Chaining"
- "Smart Thread Pool" by Ami Bar
- "Programming the Thread Pool in the .NET Framework" by David Carmona
- "Creating a Notifying Blocking Thread Pool in Java" by Amir Kirsh
- "Practical Threaded Programming with Python: Thread Pools and Queues" by Noah Gift
- "Optimizing Thread-Pool Strategies for Real-Time CORBA" by Irfan Pyarali, Marina Spivak, 더글러스 C. 슈미트 and Ron Cytron
- "Deferred cancellation. A behavioral pattern" by Philipp Bachmann
- "A C++17 Thread Pool for High-Performance Scientific Computing" by Barak Shoshany