Thread Pool, Fork-Join
대부분의 현대 운영체제는 한 프로세스가 다중 스레드를 포함하는 특성을 제공함
Multi-Thread
개념
하나의 프로세스 내에서 둘 이상의 스레드가 자원을 공유하며 동시에 작업을 수행하는 것
하나의 프로그램에서 두 가지 이상의 동작을 동시에 처리할 수 있음
웹 서버는 대표적인 멀티 스레드 응용 프로그램
ex. 사용자가 서버 데이터베이스에 자료를 요청하는 동안 브라우저의 다른 기능을 이용할 수 있음
장점
스레드는 프로세스보다 가벼움
프로세스와 달리 코드, 데이터, 스택 영역을 제외한 나머지 자원을 서로 공유하기 때문에 기본적으로 내장되어 있는 데이터 용량이 프로세스보다 작음
자원의 효율성 증대
스레드 간에 자원 공유가 가능해 프로세스 간 통신(IPC)을 사용하지 않고도 데이터 공유가 가능함
프로세스 보다 Context Switching 비용 감소
프로세스(CPU 캐시 정보 모두 교체), 스레드(공유 자원 제외한 정보만 교체)
높은 응답성(응답 시간 단축)
문제점
안정성 문제
하나의 스레드에서 문제가 발생하면 다른 스레드도 영향을 받아 전체 프로그램이 종료될 수 있음
해결 방법: 프로그래머의 역량에 따라 극복 가능
적절한 예외 처리
에러 발생 시, 새로운 스레드 생성 or 스레드 풀(Thread Pool) 잔여 스레드 이용 등
스레드 생성 문제(요청이 올 때마다 새로운 스레드 생성)
스레드 생성 비용(비쌈) + 컨텍스트 스위치 비용 발생
요청이 들어올 때마다 만들면 요청에 대한 응답 속도 느려짐
생성 제한 없이, 스레드를 무한정 생성하면 CPU 시간, 메모리 공간 등 시스템 자원이 고갈됨
해결 방법:
스레드 풀(Thread Pool)
동기화 문제(멀티 스레드와 동기화 참고)
자원을 공유하기 때문에 동기화 문제가 발생함
해결 방법: 뮤텍스, 세마포어, 모니터(Monitor) 등
스레드 풀(Thread Pool)
개념
프로세스를 시작할 때 일정한 수의 스레드를 미리 풀로 만들어두는 것
풀(Pool): 필요할 때마다 개체를 할당하고 파괴하는 대신, 사용 준비된 상태로 초기화된 개체 집합
해당 스레드들은 평소에 하는 일 없이 대기하고, 필요할 때 꺼내서 씀
기존 스레드를 재사용해서 성능을 향상시키고 스레드 생성에 따른 오버헤드를 감소시킴
필요성
프로그램 컨텍스트 스위치로 인한 성능저하를 방지하기 위해
스레드 생성/소멸을 작업(요청)이 들어올 때마다 하면 오버헤드가 발생함
다수의 사용자의 요청을 수용하고, 빠르게 처리하고 대응할 수 있음
스레드 풀에 의해 라이프 사이클이 관리되고, 작업이 큐를 이용하게 되어 우선순위가 배분되고 처리됨
동작 방식
스레드의 최대 개수를 제한하고, 정의된 스레드 수로 스레드 풀 생성
요청이 오면 작업 큐에 넣고, 해당작업을 스레드 풀의 스레드가 맡아 처리함
작업 요청이 오면 스레드를 생성하는 것이 아닌 사용 가능한 스레드를 할당함
만약 모든 스레드가 사용 중이라면 사용 가능한 스레드가 생길 때까지 작업을 대기함
작업을 완료한 스레드는 다시 스레드 풀로 돌아가 대기함
스레드 풀 장단점
장점
리소스 관리
스레드를 재사용함으로써 스레드 생성/소멸과 관련된 오버헤드를 줄여 효율적인 리소스 사용을 구현할 수 있음
오버헤드 줄임
로드 밸런싱
사용 가능한 스레드 간 작업을 분산해서 개별 스레드가 과부하되는 것을 방지함
스레드 생성보다 기존 스레드를 서비스하는 것이 종종 더 빠름
스레드 개수 제한
이 제한으로 많은 수의 스레드를 병렬 처리할 수 없는 시스템에 도움을 줌
스레드 무한정 생성 방지
단점
작업자가 스레드 개수가 충분하지 못하게 구성하면 리소스 경합이 발생해 성능 문제 발생
스레드 최대 개수를 너무 많이 설정하고 사용하지 않으면 메모리 낭비가 발생함
최적의 스레드 풀 크기 설정의 어려움
특정 애플리케이션에 대해 최적의 스레드 풀 크기를 찾는 것은 여러 요인(사용자 수, 사용 환경, 성능 등)을 고려해야 하기 때문에 어려움
스레드 풀의 단점 개선 :
Fork Join Pool
스레드 수 결정
CPU 코어의 수, 물리 메모리양, 동시 요청 클라이언트 최대 개수 등을 고려하여 정해질 수 있음
CPU 코어 수
일반적으로 스레드 수를 대략 사용 가능한 CPU 코어 수와 비슷하게 유지함
시스템이 사용 가능한 처리 능력을 최대한 활용할 수 있음
작업의 성격에 따라
작업이 계산 집약적이고 대기 시간이 크지 않은 경우: 더 많은 스레드가 유리
I/O 작업이나 대기가 많은 작업의 경우: 더 적은 수의 스레드로도 충분할 수 있음
메모리 사용량
더 정교하게 할 경우, 풀의 활용도를 보며 동적으로 풀의 크기를 바꿀 수 있음
시스템 부하가 적을 때에는 더 작은 풀을 유지할 수 있도록 함
Fork-Join
병렬 처리를 위한 공통된 모델
분할정복 알고리즘의 병렬화 버전
문제를 더 작은 하위 문제로 분할하고 병렬 실행을 위해서 여러 스레드에 분산시킴
이후 하위 문제가 해결되면 결과를 결합하여 원래 문제를 해결함
메인 부모 스레드가 자식 스레드를 생성(fork)한 다음, 자식의 종료를 기다린 후 join하고 그 시점부터 자식의 결과를 확인하고 결합하는 방법
fork()
: 프로세스(작업)을 여러 개로 쪼개서 새롭개 생성하는 작업join()
: 포크해서 생성된 프로세스/스레드의 결과를 합치는 작업
Fork Join Pool(Fork Join Framework)
개념
Java 7부터 사용가능한 Java Concurrency 툴
정확히는 Fork Join Framework이고, ForkJoinPool은 대표 클래스임
ForkJoinPool: fork-join task를 실행하는 thread pool
기존 스레드 풀을 개선하기 위한 방법으로, 스레드 풀 안에서 개별 스레드들한테 업무를 분배하는 방식
동작 방식
큰 업무를 작은 단위의 업무로 분할
부모 쓰레드로부터 처리 로직을 복사하여 새로운 쓰레드에서 분할된 업무를 수행(Fork)시킴
Fork를 반복하다가, 특정 쓰레드에서 더 이상 Fork가 일어나지 않고 업무가 완료되면 그 결과를 부모 쓰레드에서 Join하여 값을 취합함
Join을 반복하다가, 최초에 ForkJoinPool을 생성한 쓰레드로 값을 리턴하여 작업을 완료함
Work Stealing(작업 훔치기)
ForkJoinPool의 모든 스레드를 공정하게 분할하기 위한 방법
task queue로 dequeue를 이용함
다른 스레드는 바쁘게 일하고 한 스레드는 할 일이 없어졌을 때, 다른 스레드 큐의 꼬리에서 작업을 훔쳐와서 작업을 처리함
ThreadPool vs ForkJoinPool
ThreadPool은 Thread의 생성 비용을 줄이기 위해, Thread를 갖고 있는 역할
ForkJoinPool은 ThreadPool처럼 ForkJoinTask를 갖고 있는 역할을 하면서도, 각 스레드에 분담한 업무(task)를 다른 스레드에 훔쳐오는(work stealing) 역할을 함
놀고있는 쓰레드를 방지함
ThreadPool은 Runnable, Callable 객체를 갖지만, ForkJoinPool은 Runnable, Callable 객체를 한번 wrapping한 ForkJoinTask를 가짐
Last updated