열렬히.뛰기

쓰레드 라이브러리

운영체제 > Part 2 : 프로세스 관리 > 공룡책 4장 : 쓰레드 > 쓰레드 라이브러리

쓰레드 라이브러리?

프로그래머에게 쓰레드를 생성하고 관리하기 위한 API를 제공.

제공 방법에는 두 가지가 존재한다.

  1. 사용자 공간에서만 라이브러리를 제공하는 것.

    모든 코드와 자료구조는 사용자 공간에서만 존재.

  2. 운영체제에 의해 지원되는 커널 수준 라이브러리를 구현.

    이건 결국 시스템 콜을 부르는 것으로 볼 수 있다.

쓰레드 생성기법

  1. 비동기 쓰레딩
    • 부모 쓰레드가 자식 쓰레드를 낳는다.
    • 이후 부모 쓰레드가 자식 쓰레드의 종류까지 기다린다.
    • 두 쓰레드가 병렬적으로 실행. 데이터 공유 안 함.
  2. 동기 쓰레딩
    • 부모 쓰레드가 자식 쓰레드를 낳는다.
    • 이후 부모 쓰레드가 자식 쓰레드의 종류까지 기다린다.
    • 부모 쓰레드와 자식 쓰레드 사이에 상당한 데이터가 공유된다.

(여기서는 동기 쓰레딩으로 예시를 들음)

유닉스 : Pthreads

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

int sum;

int main(int ac, char **av)
{
    pthread_t       tid;    // 쓰레드 식별자 (자식 쓰레드를 의미)
    pthread_attr_t  attr;   // 쓰레드 속성

    pthread_attr_init(&attr); // 쓰레드 속성 지정
    pthread_create(&tid, &attr, runner, av[1]); // 쓰레드 실행
    pthread_join(tid, NULL); // 쓰레드 tid가 반환될 때까지 기다림.
    printf("sum = %d\n", sum); // 결과 출력
}

void    *runner(void *param)
{
    int upper = atoi(param);
    sum = 0;
    for (int i = 1; i <= upper; i++)
        sum += i;
    pthread_exit(0); // 쓰레드 종료
}

윈도우

c
#include <windows.h>
#include <stdio.h>

// DWORD = unsigned int (32비트)
DWORD sum;

DWORD WINAPI Summation(LPVOID Param)
{
    DWORD upper = *(DWORD *)param;
    for (DWORD i = 1; i < Upper; i++)
        sum += i;
    return (0);
}

int main(int ac, char **av)
{
    DWORD   ThreadId;               // 쓰레드 식별자
    HANDLE  ThreadHandle;           // 쓰레드 핸들러
    int     Param = atoi(av[1]);

    // 쓰레드 생성
    ThreadHandle = CreateThread(NULL, 0, Summation, &Param, 0, &ThreadId);
    /*
        NULL = 보안을 위한 값
        0 = 기본 스택 사이즈
        Summation = 쓰레드가 실행시킬 함수
        Param = 함수에 넣을 파라미터
        0 = 기본 플래그
        ThreadID = 쓰레드 식별자 반환
        
    */
    WaitForSingleObject(ThreadHandle, INFINITE);
    // ThreadHandle을 무한(INFINITE)시간 동안 기다린다는 뜻.

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

자바

만드는 법 (1)

java
class Task implements Runnable
{
    // 쓰레드로 돌아갈 클래스 작성하기
    // 인터페이스 Runnable을 상속받는다.

    @override
    public void run() {
        // 상속받은 run()을 재정의 : 오버라이딩
				// 원래 Runnable 클래스에 있던 애를 수정하는 작업
        System.out.println("안녕");
    }
}

public class thread
{
    public static void main(String args[]) {
        /* 쓰레드로 돌아갈 함수가 담긴 객체를 만든다. */
        Runnable run1 = new Task();

        // 쓰레드 객체를 만들고, 생성자에 함수가 담긴 객체를 넣는다.
        Thread worker = new Thread(run1);

        // run1의 run함수를 대신해줄 start함수를 실행한다.
        // 이렇게 하면 자식 쓰레드가 탄생한다.
        worker.start(); 
        
        // 자식 쓰레드를 기다린다. 
        try {
            worker.join();
				}
        catch(InterruptedException ie) {
            System.out.println("안 돌아옴.");
				}
    }
}

만드는 법 (2)

좀 더 최신 방식.

java
import java.util.concurrent.*;

class Sum implements Callable<Integer>
{
    private int upper;
    
    // 생성자 오버로딩
    public Sum (int upper) {
        this.upper = upper;
    }

    @override
    public Integer call() {
        // 상속받은 call()을 재정의
        // Runnable의 run()과 달리, 반환값이 있다.
        int sum = 0;
        for (int i = 0; i < upper; i++)
            sum += i;
        return new Integer(sum);
    }
}

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

        // 쓰레드를 만든다. (쓰레드 풀 이용)
        ExecutorService pool = Executors.newSingleThreadExecutor();

        // 클래스가 실행시킬 함수가 담긴 객체를 생성한다.
        Sum sum = new Sum(upper);

        // Callable 타입의 객체는 항상 Future 타입의 객체를 반환한다.
        // Future 객체 속 result가 자식 프로세스가 실행시킨 함수의 값이다.
        Future<Integer> result = pool.submit(sum);

        // 자식 쓰레드 반환을 기다린다.
        try {
            System.out.println("sum = " + result.get());
        }
        catch (InterruptedException | ExecutionException ie) {
            System.out.println("안 돌아옴.");       
        }
    }
}