[운영체제] 스레드 라이브러리

2 분 소요

스레드 라이브러리는 프로그래머에게 스레드를 생성하고 관리하기 위한 API를 제공한다.

스레드 라이브러리를 구현하는 방법은 두 가지가 있다.

  1. 커널 지원 없이 완전히 사용자 공간(user space)에서만 라이브러리를 제공하는 방법.
  2. 운영체제에 의해 지원되는 커널 수준 라이브러리를 구현하는 방법

첫 번째 방법은 라이브러리의 모든 코드와 자료구조는 사용자 공간에 존재한다. 따라서 라이브러리 함수를 호출하는 것은 시스템 콜이 아니라 사용자 공간의 지역 함수를 호출하는 것이다.

두 번째 방법은 라이브러리를 위한 코드와 자료구조는 커널 공간에 존재한다. 라이브러리 API를 호출하는 것은 시스템 콜을 호출하는 결과를 낳는다.

현재 아래 세 개의 스레드 라이브러리가 주로 사용된다.

1. POSIX Pthreads

사용자 또는 커널 수준 라이브러리로 제공된다. pthread.h 헤더 파일을 임포트하고 사용한다.

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

int sum;
void *runner(void *param);

int main(int argc, char *argv[]) {
	pthread_t tid;
	pthread_attr_t attr;

	pthread_attr_init(&attr);
	pthread_create(&tid, &attr, runner, argv[1]);
	pthread_join(tid, NULL);

	printf("sum = %d\n", sum);
}

void *runner(void *param) {
	int i, upper = atoi(param);
	sum = 0;

	for (i = 1; i <= upper; i++)
		sum += i;

	pthread_exit(0);
}
  • pthread_attr_init(&attr) : 스레드 속성 객체를 시스템에서 정의된 초기 값으로 초기화한다. 초기화된 스레드 속성 객체는, 이후 pthread_create() 함수의 파라미터로 전달되어 스레드를 생성할 때 사용된다.
  • pthread_create() : 스레드를 생성한다.
  • pthread_join() : 부모 스레드는 자식 스레드가 종료될 때까지 기다린다.
  • pthread_exit() : 스레드를 종료한다.

2. Windows 스레드

Windows 스레드 라이브러리는 Windows 시스템에서 사용 가능한 커널 수준 라이브러리이다. windows.h 헤더 파일을 임포트 해 사용하며, 많은 부분이 Pthreads 기법과 유사하다.

  • CreateThread() : 스레드를 생성한다.
  • WaitForSingleObject() : 이 함수를 통해 부모 스레드는 자식 스레드가 종료되기까지 기다린다.

3. Java 스레드 API

Java 프로그램에서 직접 스레드 생성과 관리 가능하다.

자바 프로그램에서는 Runnable 인터페이스를 구현해 run() 메소드를 오버라이딩 해서 스레드를 사용할 수 있다.

Class Task implements Runnable {
	public void run() {
		System.out.println("I am a thread.");
	}
}

이후 사용하고자 하는 위치에서 다음과 같은 방법으로 스레드를 생성 및 시작할 수 있다.

Thread worker = new Thread(new Task());
worker.start();

join 메소드는 Pthreads의 join 함수 및 Windows 스레드의 WaitForSingleObject() 함수와 같은 역할을 한다.

try {
	worker.join();
} catch (InterruptedException ie) { }

Executor

자바 1.5 버전에 개발자에게 스레드 생성 및 통신에 대한 제어 기능을 크게 향상시키는 몇 가지 새로운 병행 처리 기능을 도입했다. 이 도구는 java.util.concurrent 패키지를 통해 사용할 수 있다.

앞서 본 코드와 같이 Thread 객체를 명시적으로 적지 않고, Executor 인터페이스를 사용해 스레드를 생성한다.

public interface Executor {
	void execute(Runnable command);
}

인터페이스에 속한 execute() 메소드를 오버라이딩 해야 한다. Executor 는 다음과 같이 사용된다

Executor service = new Executor;
service.execute(new Task());

Callable

Runnable을 구현하는 스레드는 결과 값을 반환할 수가 없다. 이를 보완하기 위해 Callable 인터페이스가 제공되며, 결과를 반환할 수 있다는 점을 빼곤 Runnable과 유서하게 작동한다. Runnable 인터페이스의 run() 메소드와 같이 동작하는 call() 메소드를 오버라이딩 해야 한다.

Callable 작업에서 반환된 결과를 Future 객체라고 한다. Future 인터페이스에 제공되는 get() 메소드를 통해 반환된 결과를 확인할 수 있다.

import java.util.concurrent.*;

class Summation implements Callable<Integer> {
  private int upper;

  public Summation(int upper) {
      this.upper = upper;
  }

  @Override
  public Integer call() throws Exception {
      int sum = 0;
      for (int i = 1; i <= upper; i++)
          sum += i;

      return new Integer(sum);
  }
}

public static void main(String[] args) {
  int upper = Integer.parseInt(args[0]);

  ExecutorService pool = Executors.newSingleThreadExecutor();
  Future<Integer> result = pool.submit(new Summation(upper));
  
  try {
      System.out.println("sum = " + result.get());
  } catch(InterruptedException | ExecutionException e) {}
}

카테고리:

업데이트:

댓글남기기