Processes and Threads (2)

쓰레드의 사용

이전에는 프로세스의 개념밖에 없었는데, 실행하고 싶은 프로그램이 많아지고 복잡해지면서 프로세스 안의 실행의 흐름도 여러개가 있으면 좋겠다는 생각을 하게 되었습니다. 이렇게 만들어진 개념이 쓰레드입니다. 예를 들면 이 그림과 같습니다.

(커널 위에 있으므로) 일반 모드로 동작하는 프로세스가 세 갈래의 실행의 흐름이 나뉘어 각각 키보드, 디스크, ..등등의 실행에 관여하는 모습입니다.
서버를 구성하는 세 가지 방법이 있는데, 다음과 같습니다.

모델 특징
Threads 동시에 수행할 수 있고, 디스크를 읽는 동안 blocked 상태가 될 수 있습니다. 그러나 다른 쓰레드는 동작합니다.
Single-threaded process 동시에 수행할 수 없고, blocked 상태가 되면 모든 작업이 멈춥니다.
Finite-state machine, FSM 동시에 수행할 수 있고, 인터럽트나 시스템 콜에 의해 blocked 상태가 되지 않습니다.

고전 쓰레드 모델


(a)와 같은 그림으로 어떤 프로그램의 병렬을 구현하다보면 각각 프로세스와 데이터를 공유하는 것이 힘들었습니다. 그래서 연산의 결과를 다시 이용하기 어려웠습니다. 하지만 (b)와 같이 한 프로세스 안에서 다중 쓰레드 방식을 이용하면 각 CPU에 쓰레드를 할당에 빠른 연산 속도를 기대할 수 있고, 연산의 결과를 쉽게 공유할 수 있습니다. 쓰레드는 글로벌 변수를 공유하기 때문입니다.
이것은 프로세스마다, 쓰레드마다 필요한 자료구조가 정리되어 있는 자료인데 각 쓰레드마다 스택 값을 할당합니다. 그 스택에서는 로컬 변수들이나 리턴 주소를 저장합니다. 그러면 이런 그림의 형태로 프로세스가 구동하게 됩니다.

POSIX 쓰레드

다음은 vcpkg에 있는 pthread.h 헤더를 이용해 POSIX 쓰레드를 사용한 예제입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <pthread.h>
#include <stdio.h>
#define NUMBER_OF_THREADS 10
 
void *print_hello_world(void* tid) {
    printf("Hello World. from thread %d\n", (int)tid);
    pthread_exit(NULL);
    return 0;
}
int main(void) {
    pthread_t threads[NUMBER_OF_THREADS];
    int status, i;
    for (i = 0; i < NUMBER_OF_THREADS; i++) {
        printf("Main here. Creating thread %d\n", i);
        status = pthread_create(&threads[i], NULL, print_hello_world, (void*)i);
 
        if (status != 0) {
            printf("Oops. pthread_create returned error code %d\n", status);
            return -1;
        }
    }
    return 0;
}
cs