[운영체제] 암묵적 스레딩

2 분 소요

스레드의 생성과 관리 책임을 개발자로부터 컴파일러와 실행시간 라이브러리에 넘겨주는 행위암묵적 스레딩 이라고 한다.

다음은 암묵적 스레딩을 구현하는 방법이다.

1. 스레드 풀

  • 프로세스가 시작될 때 일정 수의 스레드들을 미리 만들어 두는 개념이다.
  • 스레드 생성 및 수거에 드는 비용을 줄이고, 재사용성을 높인다.
  • 간단하게 큐에 쓰레드를 몇 개 미리 만들어 넣어논 것이라고 생각하면 된다.

요청을 받았을 때 다음과 같이 작동한다. 풀에 사용 가능한 스레드가 있으면 깨어나고, 즉시 요청을 서비스한다. 풀에 사용 가능한 스레드가 없으면, 사용 가능한 스레드가 생길 때까지 작업을 대기한다.

자바 스레드 풀

  1. 단일 스레드 : newSingleThreadExecutor()는 크기가 1인 풀을 생성한다.
  2. 고정 스레드 : newFixedThreadPool(int size)는 크기가 size로 고정된 풀을 생성한다.
  3. 캐시 스레드 : newCachedThreadPool()는 많은 경우 스레드를 재사용하는 제한이 없는 스레드 풀을 생성한다.
import java.util.concurrent.*;

public class ThreadPoolExample {
	public static void main(String[] args) {
		int numTasks = Integer.parseInt(args[0].trim());

		ExecutorService pool = Executors.newCachedThreadPool();

		for (int i = 0; i < numTasks; i++)
			pool.execute(new Task());
		
		pool.shutdown();
	}
}
  • execute() : 파라미터로 Runnable 객체를 전달해 스레드 풀 내에 존재하는 스레드에게 작업을 던져준다.
  • shutdown() 스레드 풀은 추가 작업을 거부하고, 작업중이던 것들이 다 마무리되면 종료된다.

2. Fork Join 모델

큰 Task를 작은 Task들로 나누어 배분한 다음에, 일이 다 끝나면 취합하는 형태이다. 분할 정복 알고리즘과 같은 개념이다.

http://dl.dropbox.com/s/ce0ab24w5sdbkd1/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EC%95%94%EB%AC%B5%EC%A0%81%20%EC%8A%A4%EB%A0%88%EB%94%A9-1.png

Java 7버전에서 Fork Join 라이브러리를 도입했다. ForkJoinPool 객체를 생성을 통해 스레드 풀을 fork 와 join이 사용될 스레드 풀을 만든다. invoke 메소드는 주어진 가장 상위 Task를 파라미터 받아 작업 수행을 시작한다. 이후 분배된 Task들이 각각 반으로 나뉘어 재귀적으로 작업이 실행된다.

ForkJoinPool pool = new ForkJoinPool();
int[] array = new int[SIZE];

SumTask task = new SumTask(0, SIZE - 1, array);
int sum = pool.invoke(task);

위 코드에서 invoke 메소드를 호출해 배열 전체 요소를 task로 전달하고, 이후 스레드들이 나누어지고 계산된 수행 결과가 최종적으로 sum 변수에 들어가게 된다.

import java.util.concurrent.*;

public class SumTask extends RecursiveTask<Integer> {
	static final int THRESHOLD = 1000;
	
	private int begin;
	private int end;
	private int[] array;

	public SumTask(int begin, int end, int[] array) {
		this.begin = begin;
		this.end = end;
		this.array = array;
	}

	@Override
	protected Integer compute() {
		if (end - begin <  THRESHOLD) {
			int sum = 0;

			for (int i = begin; i <= end; i++)
				sum += array[i];
	
			return sum;
		} else {
			int mid = (begin + end) / 2;

			SumTask leftTask = new SumTask(begin, mid, array);
			SumTask rightTask = new SumTask(mid + 1, end, array);

			leftTask.fork();
			rightTask.fork();

			return leftTask.join() + rightTask.join();
		}
	}
}

Java의 Fork Join 모델은 Runnable 이나 Callable 을 사용하지 않는다. 대신, ForkJoinTask 라는 추상 클래스를 상속받은 RecursiveTask 와 RecursiveAction 두 클래스 중 하나를 선택하여 사용한다. Runnable과 Callable 과 마찬가지로 둘 사이의 차이점은 반환 값이 있느냐 없느냐다. RecursiveTask는 값을 반환하며, RecursiveAction은 그렇지 않다.

fork() 메소드를 통해 자식 스레드를 실행 시키고, join() 메소드를 통해 자식 스레드가 종료되기까지 기다린다. 또한, RecursiveTask 객체이므로, join() 메소드는 값을 반환한다.

카테고리:

업데이트:

댓글남기기