반응형
- 6.1 스레드에서 작업 실행
6.1.1 작업을 순차적으로 실행
6.1.2 작업마다 스레드를 직접 생성
6.1.3 스레드를 많이 생성할 때의 문제점 - 6.2 Executor 프레임웍
6.2.1 예제: Executor를 사용한 웹서버
6.2.2 실행 정책
6.2.3 스레드 풀
6.2.4 Executor 동작 주기
6.2.5 지연 작업, 주기적 작업 - 6.3 병렬로 처리할 만한 작업
6.3.1 예제: 순차적 페이지 렌더링
6.3.2 결과가 나올 때까지 대기: Callable과 Future
6.3.3 예제: Future를 사용해 페이지 렌더링
6.3.4 다양한 형태의 작업을 병렬로 처리하는 경우의 단점
6.3.5 CompletionService: Executor와 BlockingQueue의 역할
6.3.6 예제: CompletionService를 활용한 페이지 렌더링
6.3.7 작업 실행 시간 제한
6.3.8 예제: 여행 예약 포털
6.1 스레드에서 작업 실행
- 작업이 애플리케이션의 전체 기능 중에 충분히 작은 부분을 담당해야 한다.
- 서버 애플리케이션에서 대표적인 작업 단위는 클라이언트의 요청 하나
6.1 스레드에서 작업 실행
- 6.1.1 작업을 순차적으로 실행
- 웹서버가 이전 client의 요청을 처리하는 중이라면, 다음에 요청한 client는 웹 서버가 이전 작업이 끝내기만을 기다려야 한다.
- I/O 작업을 하는 동안 CPU가 대기 함.
- 적은 처리량, 느린 반응속도
6.1 스레드에서 작업 실행
- 6.1.2 작업마다 스레드를 직접 생성
- 사용자 요청이 들어올 때 마다 새로운 스레드를 생성해서 실행.
- client의 요청 내용을 메인 스레드가 직접 처리하지 않고, client가 접속할 때 마다 해당 요청을 처리하는 새로운 스레드를 생성
- 작업을 처리하는 기능이 메인 스레드로 부터 분리
- 2개 이상의 요청을 동시에 처리 가능
- 스레드 안전성 확보 필요
- 클라이언트
요청 전송 속도에 비해 응답을 스레드에 넘겨주는 속도가 빨라야 함
6.1 스레드에서 작업 실행
- 6.1.3 스레드를 많이 생성할 때의 문제점
- 스레드를 생성/제거하는 작업에도 자원이 소모 된다.
- 프로세서 보다 많은 수의 스레드가 동작 중이라면, 대부분의 스레드는 대기(idle)상태에 머무른다. 대기 상태에 스레드가 늘어나면 메모리 소모가 증가, JVM의 가비지컬렉터 부하 증가.
- 시스템에는 생성할 수 있는 스레드의 개수가 제한되어 있다.
- 특정 개수를 넘어가면 성능이 떨어지게 되므로, 애플리케이션이 생성 가능한 스레드의 수에 제한을 둬야한다
6.2 Executor 프레임웍
- Thread 보다 Executor가 훨씬 추상화가 잘되어 있다.
- 다양한 종류의 작업 실행 정책을 지원하는 유연하면서 강력한 비동기적 작업 실행 프레임웍의 근간을 이루는 인터페이스다.
- 작업 등록(task submission)과 작업 실행(task execution)을 분리하는 표준적인 방법이며, 각 작업은 Runnable의 형태로 정의한다.
- 작업의 라이프 사이클을 관리하는 기능도 갖고 있고, 몇 가지 통계 값을 뽑아내거나 작업 실행 과정을 관리하고 모니터링 가능.
- 프로듀서-컨슈머 패턴에 기반(예제. 처리할 작업을 execute 메소드로 등록해 두면 Executor 내부의 큐에 쌓이고, Executor 내부의 풀에 있는 스레드의 큐에 쌓여 있는 작업을 하나씩 뽑아내 처리하게 되어있다.)
6.2 Executor 프레임웍
- 예제 6.4 스레드 풀을 사용한 웹서버
- 100개의 고정 된 스레드를 확보하는 풀을 사용
- 요청 처리 작업을 등록하는 부분과 실제로 처리 기능을 실행하는 부분이 Executor를 사이에 두고 분리 됨.
- 처리할 작업을 execute 메소드로 등록해 두면 Executor 내부의 큐에 쌓이고, Executor 내부의 풀에 있는 스레드의 큐에 쌓여 있는 작업을 하나씩 뽑아내 처리함
6.2 Executor 프레임웍
- 6.2.2 실행 정책
- 작업을 등록하는 부분(Executor.execute() 메소드를 호출하는 부분)과 실행하는 부분(Executor.execute()메소드)을 서로 분리
- 실행정책(execution policy)를 쉽게 변경할 수 있다.
- 애플리케이션을 실제 상황에 적용하려 할 대 설치할 하드웨어와 기타 자원의 양에 따라 적절한 실행 정책을 임의로 지정할 수 있다.
- 실행정책
- 작업을 어떤 스레드에서 실행할 것인가?
- 작업을 어떤 순서로 실행할 것인가?(FIFO, LIFO, 기타 다양한 우선순위 정책)
- 동시에 몇 개의 작업을 병렬로 실행할 것인가?
- 최대 몇 개까지의 작업이 큐에서 실행을 대기할 수 있 게 할 것인가?
- 시스템에 부하가 많이 걸려서 작업을 거절해야 하는 경우, 어떤 작업을 희생양으로 삼아야 할 것이며, 작업을 요청한 프로그램에 어떻게 알려야 할 것인가?
- 작업을 실행하기 직전이나 실행 한 직후에 어떤 동작이 있어야 하는가?
6.2 Executor 프레임웍
- 6.2.3 스레드 풀
- 스레드풀(thread pool)은 처리할 수 있는 동일한 형태의 스레드를 풀의 형태로 관리.
- 작업 큐에서 실행할 다음 작업을 가져오고, 작업을 실행하고, 가져와 실행할 다음 작업이 나타날 때까지 대기하는 일을 반복.
- 스레드를 계속 생성할 필요가 없고 스레드가 이미 만들어져 있기 때문에 작업을 실행하는데 delay가 발생하기 않아 반응 속도가 향상 됨.
6.2 Executor 프레임웍
- JAVA에서 기본적으로 제공하는 스레드 풀
- newFixedThreadPool : 작업이 등록 될 때 실제 작업할 스레드를 하나씩 생성. 생성 가능한 최대 스레드 개수는 제한되어 있음.
- newCachedThreadPool : 쉬는 스레드가 많이 발생할 때 쉬는 스레드를 종료시킨다. 스레드의 수에는 제한이 없음.
- newSingleThreadExecutor : 단일 스레드로 동작.
- newScheduledThreadPool : 일정 시간 이후에 실행하거나 주기적으로 작업을 실행할 수 있다. Timer 클래스와 기능이 유사하다.
- pool을 사용하는 경우에 장점
- 웹서버에 부하가 걸리더라도 메모리가 부족해서 죽는 일은 없다.
(성능이 떨어질 때 점진적으로 서서히 떨어진다.)
- 성능 튜닝, 실행과정 관리, 모니터링, 로깅, 오류 처리에 용이하다.
6.2 Executor 프레임웍
- 6.2.4 Executor Lifecycle
- ExecutorService 인터페이스를 이용해서 lifecycle(전체 주기 중 현재의 상태 확인, 종료)를 관리할 수 있다.
6.2 Executor 프레임웍
- 6.2.4 Executor Lifecycle
- ExecutorService는 실행 중(running), 종료 중(shutting down), 종료(terminated) 3개의 lifecycle을 갖는다.
- shutdown : 새로운 작업을 등록받지 않고 이전에 등록되어 있던 작업을 끝마치고 종료된다.
- shutdownNow : 강제종료, 현재 진행 중 작업도 가능한 취소, 대기 중 작업을 실행하지 않음.
- awaitTermination : 지정된 시간만큼 작업이 끝나기를 대기함(블로킹 메소드)
- 일반적으로 종료 처리 시 shutdown 호출 후 awaitTermination 호출하는 방법 사용
6.2 Executor 프레임웍
- 6.2.4 지연 작업, 주기적 작업
- Timer 보다는 ScheduledThreadPoolExecutor 사용을 권장
- Timer의 문제점
- Timer는 등록된 작업을 실행시키는 스레드를 하나만 생성하여 특정 작업이 오래 걸리면 다른 작업도 영향을 받는다.
- TimerTask가 동작하던 중 예상치 못한 Exception이 발생하는 경우 예측하지 못한 상태로 넘어갈수 있다.
- 특별한 스케줄 방법을 지원하는 스케줄링 서비스를 구현해야 하는 경우에는 DelayQueue 사용을 권장함.
6.3 병렬로 처리할 만한 작업
- 브라우저 애플리케이션에서 웹 페이지를 그려내는 기능을 예제로 설명함.
HTML 페이지를 입력 받아 이미지 버퍼에 그림을 그려넣는 방법으로 동작한다.
6.3 병렬로 처리할 만한 작업
- 6.3.1 예제: 순차적 페이지 렌더링
- 텍스트 부분을 전부 처리하고, 텍스트 사이에 이미지는 빈 박스로 표현하면서 넘어간다.
- 이미지를 다운로드 받아 비워뒀던 공간에 그려 넣는다.
- 이미지를 다운로드 받는 작업은 I/O 작업이며, I/O 작업 시간 동안에 CPU가 하는 일은 별로 없기 때문에 CPU의 능력이 제대로 활용되지 못함.
- 처리해야할 큰 작업을 작은 작업 단위로 쪼개서 동시에 실행한다면 CPU도 잘 활용할 수 있고, 처리 속도와 응답속도도 개선.
6.3 병렬로 처리할 만한 작업
- 6.3.2 결과가 나올 때까지 대기: Callable과 Future
- Runnable 인터페이스의 단점은 run() 메소드 실행이 끝난 다음 결과 값을 리턴할 수도 없고, Exception을 throw 할 수도 없는 것이다.
- Callable 인터페이스에서 call() 메소드를 실행하고 나면 결과 값을 돌려 받을 수 있고, Exception 도 발생 시킬 수 있다.
- Future를 사용해 작업 처리 상태 및 결과값을 확인할 수 있다.
- Future.get 호출 시 작업이 완료되었으면 즉시 결과가 리턴되거나 예외가 발생된다.
작업이 완료되지 않았으면 완료될 때까지 대기한다.
작업이 완료되었으나 예외가 발생했었다면 ExecutionException 발생
작업이 중간에 취소되었다면 CancellationException 발생
Executor 에서 실행한 작업은 생성(created), 등록(submitted), 실행(started), 종료(completed) 4가지 상태를 통과한다.
반응형
'개발 > 병렬 프로그래밍' 카테고리의 다른 글
자바 병렬 프로그래밍 - 8장 스레드 풀 활용 (0) | 2016.08.30 |
---|---|
자바 병렬 프로그래밍 - 7장 중단 및 종료 (0) | 2016.08.29 |
자바 병렬 프로그래밍 - 5장 구성 단위 (0) | 2016.08.12 |
자바 병렬 프로그래밍 - 4장 객체 구성 (0) | 2016.07.31 |
자바 병렬 프로그래밍 - 3장 객체 공유 (0) | 2016.07.31 |