[운영체제] 쓰레드

정태규·2023년 6월 11일
0

운영체제

목록 보기
17/20

우선 쓰레드에 관해서 간략하게 설명하자면, 쓰레드의 사전적 의미는 '실'이다.
하나의 프로세스 안에 여러 개의 thread로 나뉘어져 있고 각 thread가 실이라고 보는 것이다. 그러면 processor가 '바늘' 처럼 thread를 실처럼 하나씩 꿰어서 바느질 해나가는 것이다. A 쓰레드를 하다가 B 쓰레드를 하고 다시 A 쓰레드를 실행하면 아까에 이어서 진행한다. 하나의 core로만 동작할때는 앞에서 설명한 것처럼 쓰레드를 하나씩 번갈아 가면서 마치 동시에 실행되는것 같은 concurrency하게 실행되지만, muli-core에서는 각 core가 동시에 thread를 실행시킬 수 있기 때문에 parallelism하게 실행시킬 수도 있다.

single and multithreaded processes


thread들은 address space를 모두 공유한다. 즉, 한쪽 쓰레드에서 데이터값을 바꾸면 다른쪽 쓰레드도 바뀐값을 알 수 있다.

Using thread

pthread_create()는 thread를 만드는 system call이다. thread를 만들어서 나온 thread id를 tid에 담고, 생성된 쓰레드가 hello() 함수를 가리킨다. 만약 전역변수인 values 배열의 어떤 값이 바뀌어도 thread는 address space를 공유하기 때문에 공유된 값들을 알 수가 있다.

thread는 결국 프로세스를 진행시키는것을 말하고, 이것을 abstraction 시키면 마치 바늘이 실을 꿰어나가는것 같다고 해서 thread이다.

single-threaded process: process 하나에 thread가 한개이다.
multi-threaded process: process 하나에 thread가 여러개이다.

thread에서의 address space


왼쪽이 single thread, 오른쪽이 multi thread이다.
multi thread 같은 경우, PC와 SP가 여러개가 있어서 thread2에서 함수호출이 일어나면 $SP(T2)에서 실행이될것이고, thread1에서 함수호출이 일어나면 $SP(T1)에서 실행이 될것이다. thread는 address space를 공유함으로 한 thread가 다른 thread에서 변경된 값들도 공유할 수 있다.

Processes vs threads

  • process가 multiple threads를 가질 수 있다.

  • 하나의 thread는 single process에 바인딩 된다.

  • process들은 thread들이 실행될 수 있는 컨테이너이다.

    PID, address space, user and group ID, open file descriptors, current working directory etc.

  • 모든 thread들은 같은 address space를 공유한다.

  • thread는 scheduling 될 수 있는 한 유닛이다.

  • process는 static하고 thread는 dynamic하다.

Multi-threading의 이점

  • concurrency 만들기가 쉽다. pthread systemcall을 호출하면 thread를 만들어준다.
  • multi-core architectures에서 사용하기가 좋다. thread가 없다면 fork를 통해서 process를 하나더 만들고 process끼리 data를 공유까지 해줬어야 하지만 그럴필요가 없다.
  • 같은 address space를 공유하기 때문에 resource를 공유하게 된다.
  • 프로그램 구조가 간단해진다. 큰 task를 여러 쓰레드로 나눠서 작업할 수 있다.
    예를들어 event를 입력을 받는 thread와 처리하는 thread로 나눠질 수 있다.
  • 한 thread가 I/O작업을 수행하는 동안 다른 thread는 다른 계산을 처리할 수 있어서 성능이 빨라진다.
  • web server에서 처럼 동시에 발생하는 이벤트들을 처리할 수 있다.

Councurrency vs Parallelism

  • concurrency: single core로 여러개의 thread를 번갈아가며 처리한다. 마치 동시에 처리되는것 같이 보이게 한다.
  • parallelism: 여러개의 코어가 동시에 thread를 처리한다.

parallelism 없이 concurrency를 갖을 수 있다. ex) 스케줄러가 single processor에서 concurrency를 제공한다.

types of Parallelism

예를들어 한 반에서 신상을 조사한다고 하자. 이름과 전화번호를 조사해야 한다.

🤔Data parallelism
조사하는 사람들은 한번에 이름과 전화번호를 물어본다.
한명은 1~15번까지의 이름과 전화번호를 물어보고
한명은 16~30번까지의 이름과 전화번호를 물어본다.

🤔Task parallelism
전체 학생에 대해서 한 사람은 이름만 물어보고 다른 한사람은 전화번호만 물어본다.

User thread - kernal thread

one-to-one

  • user thread가 만들어지면 1대1 대응되게 kernal thread가 만들어지는 것이다.
  • many-to-one보다 더 concurrency하다.
  • 만약에 user thread를 10000개 만들면 kernal thread를 10000개 만들어야 하기 때문에 overhead가 커져서 user마다 thread를 만들 수 있는 제약이 있다.
  • e.g. Linux,Windows,Solaris 9 and later

Many-to-one


many user-thread가 single keranl thread에 맵핑되어있다.
운영체제에서 thread를 하나 받아와서 user-level에서 여러개의 thread로 만들어서 돌아가는 형태로도 만들 수 있다. 그래서 user-level에서 mapping 시켜주는 라이브러리로 구현되는 경우가 많다.
하지만 문제가 있다. kerenl에서 넘겨준 thread를 user-level에서 돌려가면서 써야하는데, 어느정도 실행했다가 다음으로 넘기고 스위칭하고, 스케줄하고 하는것을 구현하기가 너무 제약적이다. 만약에, 어느 하나의 thread가 무한루프에 걸려서 다음으로 넘어가지 못한다면 다음으로 스케줄되지 않는 경우도 발생한다. 그래서 어느정도의 concurrency를 만들수는 있지만 어느 하나의 thread가 실행이 잘안되면 다른 thread까지 모조리 망해버릴 수 있어서 좋지는 않다.

이에 더해서 many-to-many도 있다.

Thread Libraries

thread를 사용자가 일일이 생성하고 관리하는 것은 어렵기 때문에 liabrary를 제공한다.
구현방법에는 두가지가 있다.
1. OS가 지원하는 kernel-level library
2. 완전히 user space에 있는 library

pthreads(POSIX Threads)

POSIX는 여러 운영체제 에서의 API의 표준을 정의해준다. 프로그램을 짰는데 어떤 다른 운영체제에서 돌려봤더니 프로그램이 먹통이라면 상당히 난감할 수 있다. 그래서 thread의 표준을 만들어 낸것이 pthreads이다.
구체적으로 구현이 되어 있는 것은 아니고, specification만 정의되어 있다.

Thread Creation/Termination

thread의 헤더를 보면 다음과 같다.

thread 예제코드


pthread_create()에서 runner 함수를 가리키는 thread를 만든다. main thread에서는 pthread_join()에 의해 runner함수가 return 할때까지 기다린다.
runner가 전역변수인 sum에 값을 수정한다면 main thread에서도 같은 sum값을 읽게 된다.

fork(),exec()

  • exec()
    exec() system call을 호출하면 해당 process의 메모리는 전부 날라가고 새로운 process에서 exec()만 실행되게 된다. thread 환경에서도 마찬가지다. thread 여러개가 실행되고 있더라도 exec()을 호출하면 thread가 모두 날라가고 single thread로써 exec()을 실행하게 된다.

  • fork()
    fork() system call을 실행하면 모든 process의 메모리를 복사해서 새로운 process를 복제해준다. UNIX에서는 thread 환경에서도 fork()는 똑같이 모든 thread를 복사해서 새로운 process를 만든다. 반면 fork1()은 내가 호출하고자 하는 thread만을 복사해서 새로운 thread를 만든다.
    LINUX에서는 fork1()만 지원하고 있다. 실행할때는 fork()를 호출하면 fork1()과 같은 기능을 갖는 함수를 호출하게 된다.

Signal handling

process가 실행되다가 signal을 받으면 interrupt가 일어나서 signal handling을 한후에 다시 돌아가서 실행된다. single thread 에서는 signal을 받았을때 처리해야될 대상이 명확한데, thread가 여러개 있다면 어떨까?? 예를들어 control key를 눌렀을때 특정 thread에서 처리하고 싶다거나, I/O만 처리하는 thread를 따로 만들고 싶을수가 있다. 그렇다면 signal을 어떤 thread에서 처리해야할지 어떻게 정할 수 있을까??
어떤 signal에서 어떤 작용을 해야하는지 적어놓은 signal handler는 모든 thread들이 공유하고 있다. 따라서 thread들은 어떤 signal을 받고 받지 않을지를 정해놓는데 이것을signal masking 이라고 한다. masking을 해놓으면 해당 signal을 받지 않는다. 즉, signal masking이 되어있지 않은 thread로 signal handling이 일어나는 것이다.

0개의 댓글