자바 병렬 프로그래밍 summary - 6. 작업 실행

프로그래밍/병렬프로그래밍 2010. 4. 8. 06:47

 개인적인 스터디를 위해 위 책의 내용을 정리한 내용입니다. 자세한 내용을 확인하고 싶으신 분은 위 책을 구매하셔서 확인하시기 바랍니다. http://www.yes24.com/24/goods/3015162?CategoryNumber=001001003


2
. 병렬프로그램 구조 잡기

6. 작업 실행

 

6.1 스레드에서 작업 실행

스레드는 특정 잡업을 비동기적으로 동작시킬 수 있는 방법을 제공한다.

작업의 병렬성을 보장받기 위해서는 해당 작업이 독립된 작업이어야 한다.

일반적으로 모든 서버는 클라이언트의 개별 요청 단위를 작업의 범위로 지정한다.

그럼으로써 작업의 독립성을 보장받으면서 작업의 크기를 적절하게 설정할 수 있다.

 

6.1.1 작업을 순차적으로 실행

하나의 스레드가 모든 요청을 순차적으로 처리하는 경우

class SingleThreadWebServer{

           public static void main(String[] args) throws IOException {

                     ServerSocket socket = new ServerSocket(80);

                     while(true){

                                Socket connection = socket.accept();

                                handleRequest(connection);

                     }

           }

}

단점 : 단일 스레드 프로그램에서는 CPU를 사용하지 않는 작업, 예를 들어 I/O, 데이터베이스 작업시에도 다른 요청들의 처리에

CPU 자원을 활용할 수 없다.

 

6.1.2 작업마다 스레드를 직접 생성

작업을 처리하는 스레드를 요청마다 하나씩 생성하게 되는 경우

class ThreadPerTaskWebServer{

           public static void main(String[] args) throws IOException{

                     ServerSocket socket = new ServerSocket(80);

                     while(true){

                                final Socket connection = socket.accept();

                                Runnable task = new Runnable(){

                                          public void run(){

                                                     handleRequest(connection);

                                          }

                                }

                               

                                new Thread(task).start();

                     }

           }

}

- 작업을 처리하는 기능이 메인 스레드에서 떨어져 나온다.

- 동시에 여러 작업을 병렬로 처리할 수 있기 때문에 두개 이상의 요청을 받아 동시에 처리할 수 있다.

- 작업에 대한 스레드 안전성을 확보해야 한다.

 

6.1.3 스레드를 많이 생성할 때의 문제점

- 스레드 라이프 사이클 문제

           스레드 생성, 제거작업에 자원이 소모된다.

           클라이언트의 요청이 간단하면서 자주 발생하는 유형일때, 새로운 스레드를 생성하는 일이 상대적으로 전체 작업에서 많은 부분을 차지할 수 있다.

- 자원 낭비

           스레드는 시스템의 자원, 특히 메모리를 소모한다. 프로세서보다 많은 수의 스레드가 만들어져 동작 중이라면 실제로는 대부분의 스레드가 대기 상태에 머무른다.

           JVM 가비지 콜렉터에 가해지는 부하가 늘어난다. CPU 사용을 위해 여러 스레드가 경쟁하게 된다.

- 안정성 문제

           스레드는 자바코드와 네이티브 코드를 실행할 수 있도록 두개의 스택을 갖는다. 일번적인 JVM 의 경우 두개의 스택을 더한 값이 0.5MB 정도 된다. 따라서 무분별하게 스레드를 생성할 경우 OutOfMemoryError 가 발생할 수 있다.

 

6.2 Executor 프레임웤

 

[Executor 인터페이스]

public interface Executor {

void execute( Runnable command);

}

 

- Executor 는 비동기적 작업 실행 프레임웍의 근간을 이루는 인터페이스이다.

- Executor 는 작업 등록 task submission 과 작업 실행 task execution 을 분리한다.

- 각 작업의 라이프 사이클을 관리하는 기능을 제공한다.

- 통계 값을 뽑아내거나 애플리케이션에서 작업 실행 과정을 관리하고 모니터링하기 위한 기능도 제공한다.

- 각 작업은 Runnable 형태로 정의한다.

- Executor의 구조는 프로듀서 - 컨슈머 패턴에 기반하고 있다.

 

6.2.1 예제 : Executor 를 사용한 웹서버

class TaskExecutionWebServer{

           private static final int NTHREADS = 100;

           private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);

          

           public static void main(String[] args) throws IOException{

                     ServerSocket socket = new ServerSocket(80);

                     while(true){

                                final Socket connection = socket.accept();

                                Runnable task = new Runnable(){

                                          public void run(){

                                                     handleRequest(connection);

                                          }

                                }

                               

                                exec.execute(task);

                     }

           }

}

 

6.2.2 실행 정책
작업을 등록하는 부분과 실행하는 부분을 분리시키면 실행 정책 execution policy를 쉽게 변경할 수 있다.

- 작업을 어느 스레드에서 실행할 것인가?

- 작업을 어떤 순서로 실행할 것인가?(FIFO, LIFO)

- 동시에 몇 개의 작업을 병렬로 실행할 것인가?

- 최대 몇 개까지의 작업이 큐에서 실행을 대기할 수 있게 할 것인가?

- 시스템에 부하가 많이 걸려서 작업을 거절해야 하는 경우, 어떤 작업을 희생양으로 삼아야 할 것이며, 작업을 요청한 프로그램에 어떻게 알려야 할 것인가?

- 작업을 실행하기 직전이나 실행한 직후에 어떤 동작이 있어야 하는가?

 

6.3.3 스레드 풀

자바 클래스 라이브러리에서 미리 정의되어 잇는 스레드 풀

- newFixedThreadPool

           제한된 갯수까지 스레드를 생성한다.

- newCachedThreadPool

           필요한 만큼 스레드를 새로 생성한다. 사용하지 않는 스레드를 제거한다.

- newSingleThreadExecutor

           작업중에 Exception 이 발생해 비정상적으로 종료되면 새로운 스레드를 하나 생성해 나머지 작업을 실행한다.

           특정작업이 진행되는 동안 메모리에 남겨진 기록을 다음에 실행되는 작업에서 가져다 사용할 수 있다.

- newScheduledThreadPool

           일정시간 이후에 실행하거나 주기적으로 실행할 작업을 처리할 수 있다.

 

6.2.4 Executor 동작 주기

Executor 의 동작 주기를 관리하기 위해 ExecutorService 에는 여러가지 메소드가 추가되어 있다.

public interface ExecutorService extends Executor {

           void shutdown();

           List<Runnable> shutdownNow();

           boolean isShutdown();

           boolean isTerminated();

           boolean awaitTermination( long timeout, TimeUnit unit) throws InterruptedException;

           ...

}

 

Executor Service 가 갖고 있는 동작 주기 : 실행중(running), 종료중(shutting down), 종료(terminated)

 

6.2.5 지연 작업, 주기적 작업

Timer , ScheduledThreadPoolExecutor

Timer 클래스는 작업을 실행하는데 하나의 스레드만을 사용한다.

 

6.3 병렬로 처리할 만한 작업

 

6.3.1 예제 : 순차적 페이지 렌더링

 

6.3.2 결과가 나올때까지 대기 : Callable Future

Runnable 인터페이스의 한계

- 실행이 끝난 다음 결과 값을 리턴해 줄 수가 없다.

- 발생된 예외를 처리할 수가 없다.

Callable 인터페이스

- 실행후 결과값을 리턴 받을 수 있다.

- Exception 도 발생시킬 수 있다.

Future

- 작업이 정상적으로 완료됐는지, 아니면 취소됐는지 등에 대한 정보를 확인할 수 있도록 만들어진 클래스이다.

- ExecutorService 를 통해 Runnable, Callable Future 로 변환할 수 있다.

 

public interface Callable<V>{

           V call() throws Exception;

}

 

public interface Future<V> {

           boolean cancel(boolean myInterrupIfRunning);

           boolean isCancelled();

           boolean isDone();

           V get() throws InterruptedException, ExecutionException, CancellationException;

           V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException;

}

 

Executor 에서는 Runnable, Callable, java.security.PriviledgedAction 등 여러가지 유형의 작업을 실행할 수 있다.

Executor 에서 처리하는 작업은 생성 created, 등록 submitted, 실행 started, 종료 completed 의 네가지 상태를 통과한다.

 

get 메소드는 작업이 진행되는 상태에 따라 다른 유형으로 동작한다.

작업 완료시 - 즉시 결과 값을 리턴하거나 Exception 을 발생(Exception ExecutionException 클래스에 담아서 던진다.)

시작전, 진행중 - 작업이 완료될때까지 대기

 

Future 에서 리턴되는 값을 사용하기까지 순차적으로 작업을 처리하는 시간동안 Future 의 작업은 병렬로 처리된다.

순차작업이 빨리 끝나는 경우에는 Future 의 작업이 끝나기를 기다리게 되며 순차작업이 오래 걸리는 경우에는 Future 에서 처리된

결과를 바로 가져다 사용할 수 있다.

 

6.3.5 CompletionService : Executor BlockingQueue 의 연합

CompletionService   Executor 의 기능과 BlockingQueue 의 기능을 하나로 모은 인터페이스이다.

 

private class QueueingFuture<V> extends FutureTask<V> {

           QueueingFuture(Callable<V> c){ super(c); }

           QueueingFuture(Runnable t, V r){ super(t, r); }

          

           protected void done(){

                     completionQueue.add(this);

           }

}

 

6.3.6 예제 : CompletionService 를 활용한 페이지 렌더링

 

6.3.7 작업 실행 시간 제한

작업 싫행 시간 제한을 두기 위해 고려해야 할 부분

- 제한된 작업시간이 지날 경우 알리는 방법

- 제한된 작업시간 경과시 해당 작업을 중단

 

Future 사용해서 구현

try{

           f.get( timeLeft, NANOSECONDS); //Futrue task 에 제한된 시간을 설정한다.

}

catch( TimeoutException e){

           f.cancel(true); // TimeoutException 이 발생했을 때, 즉 제한된 시간을 경과했을 때 작업을 중단시킨다.

}

: