# 10. Thread(스레드)

* Thread(스레드)
  * Light Weight Process 라고도 함
  * 프로세스
    * 프로세스간에는 각 프로세스의 데이터 접근이 불가
    * 프로세스 간 커뮤니케이션 위해서는 IPC 기법 필요
  * 스레드
    * 하나의 프로세스에 여러 개의 스레드 생성 가능
    * 스레드들은 동시 실행 가능
    * 프로세스 안에 있으므로, 프로세스의 데이터들을 모두 접근 가능
    * IPC 사용할 필요 없음
  * 멀티 스레드
    * 소프트웨어 병행 작업 처리를 위해 멀티 스레드를 사용
    * 각 스레드의 CODE, DATA, HEAP영역을 서로 공유하고, thread Stack영역만 별도로 각 스레드가 가지고 있다.

> 멀티 태스킹 / 멀티 프로세싱 / 멀티 스레드\
> 1\. 멀티 태스킹: 단일 CPU에서 여려 프로세스를 실행시켜 동시에 실행되는 것처럼 보임\
> 2\. 멀티 프로세싱: 여러 CPU를 사용하여, 하나 이상의 프로세스를 빠르게 실행하여 속도를 높임\
> 3\. 멀티 스레드: 하나의 프로세스를 여러 개의 스레드를 사용\
> 따라서 최근 CPU는 멀티 코어를 가지므로, 여러 스레드를 만들어서 멀티 코어의 활용도를 높이고 있다.

* 스레드 장단점
  * 장점
    1. 사용자에 대한 응답성 향상
    2. 자원 (공유) 효율
       * IPC기법과 같이 프로세스 간 자원 공유를 위해 번거로운 작업 필요 X
       * 프로세스 안에 있으므로, 프로세스 데이터를 모두 접근 가능
    3. 작업이 분리되어 코드가 간결
       * 이건 작성하기 나름이기는 함.
    4. CPU 활용도 높이고 성능을 개선할 수 있다.
  * 단점
    1. 스레드 중 하나의 스레드만 문제가 생기더라도 전체 프로세스가 영향을 받음
    2. 스레드 많이 생성 시, Context Switching이 많이 일어나 성능 저하 일어날 수 있다.
       * 예: 리눅스 OS는 스레드를 프로세스와 같이 다룬다.
       * 따라서 스레드를 많이 생성할수록 모든 스레드에 대해 스케쥴링하면서 컨택스트 스위칭이 빈번할 수밖에 없다.

* Process VS Thread

  |     Process    |         Thread         |
  | :------------: | :--------------------: |
  |    프로세스는 독립적   | 스레드는 프로세스의 서브셋(종속되어있음) |
  | 각각 독립적인 자원을 가짐 |     **프로세스의 자원 공유**    |
  |  자신만의 주소 영역 가짐 |        주소 영역 공유        |
  | 프로세스간 IPC기법 통신 |      IPC 기법 필요 없음      |

* 스레드 동기화 문제
  * 동기화(Synchronization)
    * 스레드의 실행 순서는 정해진 게 아니라, 스케쥴러에 따라 그때마다 다름
    * 그런데 한 프로세스 내부의 스레드들은 같은 데이터를 공유하고 있음
    * 그래서 순서가 꼬이면, 비정상 동작을 할 수 있음.&#x20;
    * 이 비정상 동작을 스레드 동기화라고 하기 때문에 관리가 필요하다.
    * 정리:
      * 동기화: 작업들 사이에 *실행시기* 를 맞추는 것.
      * 여러 스레드가 동일한 자원(데이터) 접근 시 동기화 이슈 발생
        * 동일한 자원을 여러 스레드가 동시 수정 시 각 스레드 결과에 영향(비정상 동작)을 준다.
    * 해결 방법
      * **mutual exclusion(상호 배제)**
      * 스레드는 프로세스 모든 데이터를 접근할 수 있으므로,
        * 여러 스레드가 변경하는 공유 변수에 대해 exclusive access필요
        * 어느 한 스레드가 공유 변수를 갱신하는 동안 다른 스레드가 동시 접근하지 못하도록 막기
      * example02참조
        * 한번에 한 스레드만 공유 자원에 접 하도록 만들어주면 해결(lock)

          ```python
            lock.acquire()
            for i in range(100000):
                g_count +=1  #임계자원
            lock.release()
          ```
        * 임계 영역(critical section): 동시 실행하면 안되는 영역
        * 임계 자원(critical resource): 동시 수정하면 안되는 자원(g\_count)

* Mutex와 세마 포어(Semaphore)
  * Critical Section(임계 영역) 에 대한 접근을 막기 위해 Locking 매커니즘이 필요
    * Mutex(binary semaphore)
      * 임계 구역에 하나의 스레드만 들어갈 수 있음
      * 한 쓰레드, 프로세스에 의해 소유 될 수 있는 Key를 기반으로 한 상호 배제 기법
    * Semaphore
      * 임계구역에 여러 스레드가 들어갈 수 있음
      * counter를 두어 동시에 리소스에 접근할 수 있는 허용 가능한 스레드 수 제어
      * 현재 공유 자원에 접근할 수 있는 쓰레드, 프로세스의 수를 나타내는 (제한)값을 두어 상호 배제를 달성하는 기법
      * 구현(수도 코드)
        * P: 검사(임계영역에 들어갈 때) = lock.aquire()
          * S값이 1 이상이면, 임계 영역 진입 후 S값 1차감(S값이 0이면 대기)
        * V: 증가(임계 영역에서 나올 때) = lock.release()
          * S값을 1 더하고, 임계 영역을 나옴
        * S: 세마포어값( 초기 값만큼 여러 프로세스가 동시 임계 영역 접근 가능)

          ```cpp
            P(S): wait(S){
                while S<=0; //대기(loop, CPU실행, 성능 ↓)
                S--;        // 다른 프로세스 접근 제한
            }
            V(S): signal(S){
                S++;   // 다른 프로세스 접근 허용
            }
          ```
        * wait()은 S가 0이면 임계 영역에 들어 가기 위해 반복문 수행
          * 바쁜 대기, busy waiting.(위 예제)
          * 운영체제 기술로 보완: 대기 큐(아래 예제)
            * S가 음수일 경우 바쁜 대기 대신, 대기 큐에 넣는다.
            * loop를 돌리지 않고, 대기 큐에 넣어둔 후 block.
            * wakeup()함수를 통해 대기 큐에 있는 프로세스 재 실행.
            * 이렇게 하면 CPU부하를 줄이면서도 semaphore를 처리 할 수 있다.

              ```cpp
              wait(S){
              S->count--;
              if(S->count<0){
                add this process to S->queue;
                block(); 
              }
              }
              signal(S){
              S->count++;
              if (s->count>1){
                remove a process P from S->queue;
                wakeup(P)
              }
              }
              ```
    * [참고자료](https://worthpreading.tistory.com/90)
    * 참고: 주요 세마포어 함수(POSIX세마포어)
      * sem\_open(): 세마포어 생성
      * sem\_wait(): 임계 영역 접근 전, 세마포어를 잠그고, 세마포어가 잠겨 있다면 풀릴때까지 대기
      * sem\_post(): 공유 자원에 대한 접근이 끝났을 때 세마 포어 잠금을 해제한다.

* 교착상태(Deadlock)와 기아상태(Starvation)
  * 교착상태(deadlock)이란?
    * 무한 대기 상태: 두 개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리기 때문에(waiting), 다음 단계로 진행 못하는 상태
    * 여러 프로스세스가 동일 자원 점유를 요청 할 때 발생

      > 배치 처리 시스템에서는 일어나지 않는 문제 프로세스 , 스레드 둘 다 이와 같은 상태 일어날 수 있음
    * 참고: 아래 4개지 조건 모두 성립하면 교착 상태 발생 가능성 있음
      * 상호 배제: 프로세스들이 필요로 하는 자원에 대해 배타적 통제권 요구
      * 점유 대기: 프로세스가 할당된 자원을 가진 상태에서 다른 자원을 기다림
      * 비 선점: 프로세스가 어떤 자원의 사용이 끝날 때 까지 그 자원을 뺏을 수 없음
      * 순환 대기: 각 프로세스는 순환적으로 다음 프로세스가 요구하는 자원을 가지고 있음
      * 위 4가지 조건 중 한 가지가 성립 안 되도록 빼면, 교착 상태를 방지할 수 있음.
  * 기아 상태(starvation)
    * 특정 프로세스의 우선순위가 낮아서 원하는 자원을 계속 할당 받지 못하는 상태
    * 여러 프로세스가 부족한 자원을 점유하기 위해 경쟁 할 때, 특정 프로세스는 영원히 자원이 할당 안되는 경우를 주로 의미.
    * 해결 방안
      * 우선 순위 변경
        * 프로세스 우선순위를 수시로 변경해서, 각 프로세스가 높은 우선순위를 가질 기회 주기
        * 오래 기다린 프로세스의 우선순위 높여주기
        * 우선순위가 아닌, 요청 순서대로 처리하는 FIFO기반 요청 큐 사용.
