일기 대신 코드 슬쩍

[운영체제] Threads & Concurrency 본문

OS

[운영체제] Threads & Concurrency

코코자 2024. 4. 11. 14:22

이번 장에서는 threads에 대해 배운다.

뭘 배울 건지 살펴보자.

  • thread를 구성하는 기본 component가 무엇인지: thread와 precess의 차이를 중점으로
  • multithreaded process의 장단점
  • threading의 thread pools, fork-join, Grand Central Dispatch
  • Linux에서의 threads

그럼 가보자고.


4.1 Overview

Threads

Thread가 뭔지부터 알아보자.

  • Thread는 CPU를 사용하는데 있어 가장 기본적인 unit이다!
  • thread는 thread ID, PC, register set, stack으로 구성되어 있다.
  • thread는 code, data, 그리고 open files와 signal과 같은 OS 자원을 공유한다.

만약 multiple threads라면, 한번에 여러 작업이 가능하다. 아래 그림을 살펴보자.

그림에서 볼 수 있듯,

  • code, data, files는 process당 하나 존재하고
  • register, PC, stack은 thread 별로 존재한다.

그래서 왼쪽의 single-threaded process는 하나의 process와 다를게 없다.

Motivation: multithread application의 몇 가지 예시

  • 여러개의 이미지가 있을 때, 각 이미지에 대해 thread가 썸네일을 따는 작업을 수행하여 여러개를 동시에 수행함
  • web browser: web browser에서 이미지나 텍스트를 보여주는 것과, data를 가져오는 것을 동시에 수행 가능!

특히 multicore에서 multithread의 성능을 더 높일 수 있다. 그 예시도 보면

web server에서 single procss로 동작할 때, 각 request가 올 때마다 분리된 process를 만든다. 근데 이 분리된 process도 결국 request를 처리하는 동일한 동작을 수행한다. 이때, 별도의 process를 생성하는 것보다 별도의 thread를 생성하는 것이 훨씬 유리하다.

Multithread의 장점

  • Responsiveness(응답성)
    • process일부가 blocked일 때, 나머지 thread는 여전히 잘 동작한다.
    • UI 측면에서 굉장히 중요함
  • Resource sharing
    • thread가 memory와 resource를 서로 공유함
  • Economy
    • thread가 시간, memory 측면에서 process 생성보다 good임
    • context switching이 일반적으로 빠름
  • Scalability
    • 서로 다른 core를 사용중일 때, thread를 병렬적으로 늘려서 사용가능함

4.2 Multicore Programming

multicore인 컴퓨터에서 programming을 진행하는 방식에 대해 알아보자.

Multicore

multicore는 single processing chip에서 multiple computing cores가 있는 거다.

multithreaded programming은 concurrency를 증가시킴으로써 multicore를 효과적으로 운영하는 mechanism을 제공한다.

Concurrency는 모든 task가 수행중인것, 다중실행을 뜻한다.

  • multicore의 concurrency: 몇 thread는 parallel하게 수행됨
  • single core의 concurrency: 아주 미세한시간(<0.1초)내에 빠르게 전환되는 거…

Challenges

  • Identifying tasks
    • 서로 independent한 task만이 parallel하게 수행될 수 있기 때문에, dependency가 없는 task를 잘 구분해야 함
  • Balance
    • 하나의 thread에 너무 치중해서 몰빵하지 않도록 task를 적절히 분배해야함
  • Data splitting
    • 별도의 core에서 실행할 data를 잘 분리해줘야 함
  • Data dependency
    • data dependency를 수용할 수 있도록 task 순서를 적절히 배치하고, synchronization
  • Testing and debugging
    • testing과 debugging 과정이 singlecore보다 더 어려움!

▶️ parallel programming이 중요함!

 

Amdahl’s Law

암달의 법칙이 여기서도 적용이 된다.

  • S : portion for serial execution (data dependency가 있는 경우)
  • N: # of processing cores

그래프를 보면 S가 클수록 speedup은 더 작아지는 것을 확인할 수 있다.

즉, data dependency가 클수록 speedup이 어렵다.

Types of Parallelism

parallelism은 두가지로 나뉜다. data와 task.

두 개를 병행해 hybrid하게 사용하기도 한다.

  • Data parallelism
    • multicore에 data를 어떻게 잘 분산시키느냐!
    • 각 core에서 어떻게 동일연산을 수행할 것이냐!

  • Task parallelism
    • 어떤 작업을 하도록 분산을 시키냐!
    • 각 thread는 서로 다른 task를 수행하고 이때의 사용되는 data가 같을 수도 아닐 수도 있음


4.3 Multithreading Models

thread는 크게 2가지로 구분할 수 있다.

  • user threads
  • kernel threads

각각의 thread 정의를 좀 살펴보면

User threads는 kernel의 support없이, user program에 의해 실행하고 관리된다.

그에 반면해 Kernel threads는 OS가 직접적으로 관리한다. 현재 대부분은 다 Kernel thread이다.

그렇다면, user thread와 kernel thread를 연결하는 몇 가지 모델을 알아보자.

Many-to-One Model

  • Many-to-One model은 여러개의 user threads를 하나의 kernel threads에 매핑하는 model이다.
  • Thread 관리가 user space에 있는 thread library에 의해 관리되기 때문에, 만약 한 thread가 system call에 의해 block이 된다면 매핑된 kernel thread도 block 되기때문에 전체 process가 봉쇄된다.
  • 한번에 하나의 user thread만 kernel에 접근할 수 있어서, multicore system이어도 parallel하게 작동할 수 없다.

One-to-One Model

  • 하나의 user thread와 하나의 kernel thread에 각각 매핑한다.
  • 하나의 user thread가 system call에 의해 block이어도 나머지는 문제없이 수행될 수 있다.
  • multiprocessor에서 parallel를 실행한다.
  • 단점으로는, user thread를 생성할 때 kernel thread도 생성되므로 너무 많아지면 응용 프로그램의 성능이 저하된다.
  • 보통 Linux, window에서 사용한다.

Many-to-Many Model

  • 여러개의 user thread를 그보다 작거나 같은 kernel thread에 매핑한다.
  • Many-to-One과 One-to-One model의 단점을 보완한 model이다.
  • core 수에 따라 kernel thread수를 결정한다.
  • 구현하기에 까다롭다.

Two-Level Model

  • Many-to-Many + One-to-One
  • 반드시 실행되어야 할 thread→ one-to-one으로, 그 외의 thread → many-to-many로 구현한다.


4.4 Thread Libraries

thread library는 프로그래머에게 thread를 생성 및 관리하기 위한 API를 제공한다.

  • user-level library: local function call
  • kernel-level library: system call

[대표적인 thread library]

  • POSIX Pthreads: Linux에서 씀, user-level,kernel-level 다 가능
  • Windows: kernel-level library
  • Java thread API

threading에는 asynchronous threading과 synchronous threading이 있다.

  • Asynchronous threading
    • parent와 child process가 concurrent하게 실행되고, independent하다
    • data sharing이 거의 없음
  • Synchronous threading
    • parent는 child가 terminated 될 때까지 기다림
    • thread사이의 data sharing이 많을 때 사용하는 threading

그러면 간단하게 Pthread API를 사용하는 multithread C programming을 살펴보자.

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

// the data shared by the threads
int sum;

// thread call this function
// java api에서는 run method
void * runner(void* param);

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

    if(argc!=2)
    {
        fprintf(stderr, "usage: a.out <integer value>\\n");
        return -1;
    }
    if(atoi(argv[1])<0)
    {
        fprintf(stderr,"%d must be >= 0\\n",atoi(argv[1]));
        return -1;
    }

    pthread_attr_init(&attr);    // attr 초기화
    pthread_create(&tid, &attr, runner, argv[1]);   // 쓰레드 생성
    pthread_join(tid, NULL);    // tid 쓰레드 대기

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

void *runner(void* param)
{
    int i, upper = atoi(param);
    sum = 0;
    for(i=0; i<=upper; i++)
    {
        sum += i;
    }
    pthread_exit(0);
}

해당 코드에서 thread는 2개가 생성되었다.

main()함수를 실행하는 thread, runner()를 실행하는 thread

main() 함수는 runner() 함수를 실행하는 두번째 thread를 생성하고, 두 개의 thread는 sum이라는 global variable를 공유한다.

  • pthread_t tid; : 생성할 thread를 위한 identifier를 선언한다. 각각의 thread는 stack의 크기와 스케쥴링 정보를 포함한 속성의 집합을 소유한다.
  • pthread_attr_t attr; : thread를 위한 속성을 나타낸다.
  • pthread_attr_init(&attr); : atttr이 가리키는 주소에 속성을 지정한다. 여기서 default 속성을 지정한다.
  • pthread_create(&tid, &attr, runner, argv[1]); : identifier, 속성, 실행할 함수, 실행할 함수의 매개변수를 지정하여 thread를 생성한다.

main() 함수를 실행하는 parent thread는 pthread_join() 함수를 호출하여 child thread가 종료되기를 기다린다. child thread가 runner() 함수를 마치고 복귀하면 parent thread는 sharing data인 sum의 값을 출력하고 실행을 종료한다.

'OS' 카테고리의 다른 글

[운영체제] CPU Scheduling  (1) 2024.05.01
[운영체제] Threads & Concurrency (2)  (1) 2024.04.18
[운영체제] Processes(2)  (2) 2024.04.03
[운영체제] Processes  (0) 2024.04.01
[운영체제] OS 구조  (0) 2024.04.01