티스토리 뷰

Thread?

- A single unique execution context

- Mechanism for concurrency (동시성) + can also run in parallel (병렬성)

- Protection (보호) → 쓰레드 간 자원 접근을 제어하고 안전하게 관리하기 위한 메커니즘

 

MTAO (multiple things at once)

- 운영 시스템은 프로세스, 인터럽트, 백그라운드 시스템 유지 등 다양한 것들을 한 번에 핸들링할 수 있어야 함

- 네트워크 서버, 병렬 프로그램, UI를 지닌 프로그램, 그리고 network/disk bound 프로그램도 마찬가지

→ 이러한 것들을 가능하게 해주는 것이 바로 Thread

스레드는 동시성의 유닛이고, 각각의 쓰레드는 하나 혹은 한 태스크를 표현할 수 있음!

 

Multiprocessing vs Multiprogramming

여러 CPU가 일들을 하나씩 맡아서 작업하면, 같은 시각에 실행되는 프로세스가 여러 개

→ Mutiprocessing

 

한 CPU 내에서 일들이 서로 번갈아가면서 하거나 툭툭 끊어져서 진행, 즉 같은 시각에 실행되는 프로세스는 단 하나

→ Multiprogramming / Multithreading

 

Concurrency는 Parallelism이랑 같지 않다!

Concurrency는 여러 일을 한 번에 제어할 수 있는 것

Parallelism은 여러 일들이 동시에 실행하는 것

 

예를 들어, 싱글 코어 (CPU가 한 개인 경우)에서 두 스레드가 실행된다면, 이는 동시적인 것이지 병렬적인 것이 아님.

 

Thread의 3가지 상태

- Running : 현재 실행되고 있음

- Ready : 실행되기 위해 기다리고 있음

- Blocked : 입출력 작업을 수행, 동기화 등 cpu를 필요로 하지 않는 상황에서 자원을 낭비하지 않음

 

 

Blocked? 그냥 Running과 Ready만 있어도 되는 게 아닌지?

→ 예를 들어 입출력 완료를 첫 번째 스레드가 기다리고 있다고 해보자. 이 첫번째 쓰레드를 ready 상태로 바꾼다면 cpu 자원을 낭비하게 될텐데, 사실 입출력이 완료되길 기다리는 동안에는 굳이 cpu를 점유할 필요가 없다. 따라서 완료가 될 때가지 blocked 상태로 있는다면 다른 쓰레드가 먼저 cpu를 쓰면서 효율적인 이용이 가능할 것이다.

 

pthreads

병렬적으로 작동하는 소프트웨어의 작성을 위해서 제공되는 표준 API인 pthreads를 통해 스레드의 동작을 파악할 수 있다. pthreads 라이브러리 내장 함수를 알아보도록 하자.

 

1) 생성

include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);

 

스레드를 생성하는 함수이다.

 

첫 번째 인자는 스레드를 가리키는 포인터,

두 번째 인자는 스레드의 특정 옵션을 설정(대부분의 경우 NULL 입력),

세 번째 인자는 스레드가 수행할 함수,

네 번째 인자는 세 번째 인자에 들어가는 함수가 필요로 하는 내부 인자이다.

 

2) 종료

void pthread_exit(void *value_ptr);

 

스레드를 종료할 때 쓰는 함수이다. 보통 인자에는 NULL이나 0이 들어간다.

 

3) 조인

int pthread_join(pthread_t thread, void **value_ptr);

 

타깃 스레드 (첫 번째 인자)가 종료될 때까지 기다리는 함수라고 생각하면 쉽다. 만약 메인 스레드에서 파생된 스레드들보다 메인 스레드가 먼저 종료된다면, 원하는 작업을 끝내지 못한 채 종료될 수 있기 때문이다. 두 번째 인자는 조인 함수의 반환값을 담을 포인터인데, 이 값을 통해 메인 스레드에서 다른 스레드의 상태를 확인할 수 있다. 또 이렇게 전체 업무를 여러 개의 작은 업무로 나누어 처리하고 합치는 과정을 Fork-Join Pattern이라고 한다.

 

pthread 실행 예제

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

int common = 162;

void *threadfun(void *threadid) {
    long tid = (long)threadid;
    printf("Thread #%lx stack: %lx common: %lx (%d)\n", tid, (unsigned long) &tid, (unsigned long) &common, common++);
    pthread_exit(NULL);
}

int main(int argc, char *argv[]) {
    
    long t;
    int nthreads = 2;

    // 인자를 입력하면 그만큼 쓰레드를 생성함. Default: 2
    if (argc > 1) {
        nthreads = atoi(argv[1]); //argument to integer
    }

    pthread_t *threads = malloc(nthreads*sizeof(pthread_t)); // 쓰레드의 개수만큼 위치 동적 할당
    printf("Main stack: %lx, common: %lx (%d)\n", (unsigned long) &t, (unsigned long) &common, common);
    for (t = 0; t < nthreads; t++) {
        int rc = pthread_create(&threads[t], NULL, threadfun, (void *)t);
        if (rc) { // 보통 쓰레드 생성에 에러가 발생한다 -> return value != 0
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }

    for (t = 0; t<nthreads; t++) {
        pthread_join(threads[t], NULL);
        printf("%ld\n", t);
    }

    pthread_exit(NULL);
    return 0;
}

 

스레드의 전체 흐름을 알아볼 수 있는 예제 코드이다. 실행 파일을 만들어 생성할 쓰레드의 개수를 입력해 주면 어떤 순서로 스레드가 실행되고 종료되는지를 확인해 볼 수 있다.

 

Review Point

만약 위 코드를 통해 스레드를 4개 생성한다고 했을 때, 아래 질문에 대답해 보자.

 

1. 이 프로그램에서 총 몇 개의 스레드가 사용되었는가?

- 5개가 사용되었다. 한 개의 메인 스레드와 4개의 생성된 스레드.

2. 메인 스레드는 다른 스레드들의 생성 순서와 동일한 순서로 조인하는가?

- 위 코드에서는 for문을 통해 순차적으로 조인하고 있다. 그러하다.

3. 생성된 스레드들은 그들이 만들어진 순서대로 종료되는가?

- 아니다. 위 코드 상에서는 먼저 처리되는 스레드는 바로 종료된다.

4. 프로그램이 다시 실행된다면 결과가 달라지는가?

- 그러하다. 운영체제의 스레드 스케줄러의 스케줄링에 의해 결정된다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함