쓰레드 풀이란?

알아보기 전 개념정리

  • 프로그램: 어떤 목적을 달성하기 위해서 컴퓨터의 동작들을 하나로 모아 놓은 것
  • 프로세스: 컴퓨터가 현재 실행중인 프로그램
  • 쓰레드: CPU Core의 쓰레드 실행 단위. Unit of Execution이라고도 불림
    • 쓰레드 단위로 나눠진 프로세스의 작업들을 CPU Core가 처리
    • 하나의 프로세스에서 두 가지 이상 작업을 동시에 실행이 가능함

Thread만으로 동시에 여러 작업을 실행시키는 프로그램을 만들기

- 해당 예시의 경우 자바로 만든 프로세스는 요청이 올 때마다 새로운 쓰레드를 생성해서 작업를 처리하고 처리한 후에는 쓰레기를 처리 - 하지만 쓰레드를 이렇게 사용하게 되면 문제점이 여러개가 생김 ### 1. Thread 생성 비용 - Thread 자체가 생성 비용이 크기 때문에 요청에 대한 응답시간이 늘어남 ![](https://i.imgur.com/KiSX8ef.png)
  • 자바같은 경우 One-to-One Threading Model로 쓰레드를 생성하게 되는데, 이는 User Thread가 생성시에 OS Thread와 연결해줘야 한다는 의미
  • 사실은 이 유저 쓰레드는 OS Thread에 대한 유저 프로그램 계층에서의 추상화라고 볼 수 있는데 자바는 One-to-One Threading Model이기 때문에 User Thread를 생성하게 되면 OS 커널 레벨에 있는 OS 쓰레드와 꼭 하나가 연결되어야 하는 형태
  • 그렇기 때문에 새로운 쓰레드의 생성은 OS 커널의 작업이 동반된다는 것을 의미함. 이 말인 즉슨, 새로운 쓰레드가 생성될 때 생성 비용이 많이 들게 되고 그에 따라서 작업이 요청할 때마다 새로운 Thread 생성에 대한 요청 처리 시간을 감수해야한다는 의미기도 함

2. CPU의 오버헤드 증가, 메모리 문제 발생 가능성

  • Process의 처리 속도보다 빠르게 요청이 쏟아져 들어올 경우에는 새로운 쓰레드가 이에 맞춰 계속해서 생산되는 방식이며, 무제한적으로 쓰레드가 쌓이게 될 수밖에 없음
  • 쓰레드가 많이 쌓이게 되면 기본적으로 메모리를 많이 차지하게 되고 곧 메모리 문제 발생 가능성을 높임
  • 이와 동시에 context-switching이 많이 발생할 수밖에 없기 때문에 CPU 오버헤드가 증가하는 문제가 생김

Thread Pool

  • 이러한 쓰레드의 무한정적인 생산을 막기 위해 생긴 개념이 쓰레드 풀
  • 미리 일정 개수의 쓰레드를 생성하여 관리하는 기법
  • 작업이 발생하면 대기 중이 쓰레드 하나를 선택하여 작업 수행
  • 작업이 완료되면 쓰레드는 다시 대기 상태로 돌아가고, 새로운 작업을 할당받을 준비를 함
  • 미리 만들어 놓은 쓰레드를 재사용
  • 쓰레드 생성 및 삭제에 따른 오버헤드를 줄이며, 특정 시점에 동시에 처리할 수 있는 작업의 개수 제한 → 시스템 자원을 효율적으로 관리하고 성능 향상

장점

  • 자원 효율성
    • 정해진 개수의 쓰레드 생성 및 관리(재사용) → 쓰레드 생성 및 삭제에 따른 오버헤드를 줄임
    • 자원의 효율적 관리, 불필요한 자원 소모 방지
  • 응답성 및 처리량 향상
    • 작업을 대기 상태로 유지하여 작업 처리 속도 향상
    • 작업이 발생하면 대기 중인 쓰레드 중 하나를 선택하여 작업을 할당 → 작업 처리를 병렬적으로 진행
  • 작업 제어
    • 동시에 처리할 수 있는 작업의 개수 제한
    • 쓰레드 풀의 크기를 조절하여 시스템 부하 조절, 과도한 작업 요청으로 인한 성능 저하 방지
  • 쓰레드 관리
    • 쓰레드의 생명주기를 관리
    • 쓰레드 풀은 쓰레드의 생성, 재사용, 종료 등을 관리하여 쓰레드의 안전한 운영에 용이

Node.js에서의 쓰레드 풀

  • 노드의 쓰레드 풀 같은 경우 기본적으로 libuv 라이브러리에서 관리하고 있음
  • 각 스레드는 I/O 작업을 처리하고, 작업이 완료되면 콜백함수를 호출하여 결과 반환

쓰레드 풀 기본 설정값

  • node.js의 기본적인 쓰레드 개수는 4로 설정되어 있으며, 이를 변경하기 위해서는 nodejs의 EntryPoint에서 지정해줘야 함

UV_THREADPOOL_SIZE 설정 방법

Windows CLI

$ env:UV_THREADPOOL_SIZE=<value>
$ node test.js

macOS/Linux

$ export UV_THREADPOOL_SIZE <value>
$ node test.js

Thread Pool 용도

  • CPU 또는 I/O intensive 한 작업에 사용
    • I/O-intensive : DNS, File System
    • CPU-intensive : Crypto, Zlib
    • Application and modules : use C ++ add-on
  • 위와 같이 nodejs eventloop 에서 처리하기에 무거운 작업이 자동으로 thread pool 내의 thread 에 위임

예제 코드

import {pbkdf2} from 'crypto'
import * as process from 'process'
 
console.log('worker thread number is : ' + process.env.UV_THREADPOOL_SIZE)
 
console.time('Hashing1')
pbkdf2('a', 'b', 100_000, 512, 'sha512', ()=>{
  console.timeEnd('Hashing1')
})
 
console.time('Hashing2')
pbkdf2('a', 'b', 100_000, 512, 'sha512', ()=>{
  console.timeEnd('Hashing2')
})
 
console.time('Hashing3')
pbkdf2('a', 'b', 100_000, 512, 'sha512', ()=>{
  console.timeEnd('Hashing3')
})
 
console.time('Hashing4')
pbkdf2('a', 'b', 100_000, 512, 'sha512', ()=>{
  console.timeEnd('Hashing4')
})
 
console.time('Hashing5')
pbkdf2('a', 'b', 100_000, 512, 'sha512', ()=>{
  console.timeEnd('Hashing5')
})

(1) 쓰레드풀 기본값(4)로 5개 해시함수 실행

worker thread number is : undefined
Hashing3: 351.294ms
Hashing4: 361.309ms
Hashing1: 366.053ms
Hashing2: 371.932ms
Hashing5: 658.325ms
  • 기본값이 4개기 때문에 총 1~4번 해싱작업은 동시에 처리되고 5번은 이후에 쓰레드가 할당되어 처리됨

쓰레드풀 (5)개로 해시함수 5개 실행

# 쓰레드풀 사이즈 5 지정
$env:UV_THREADPOOL_SIZE=5
worker thread number is : 5
Hashing4: 318.785ms
Hashing5: 328.197ms
Hashing2: 329.408ms
Hashing1: 347.599ms
Hashing3: 360.894ms
  • 쓰레드풀 5개를 모두 사용하여 5개 해싱작업이 동시에 이뤄짐