본문 바로가기
개발공부 개발새발/OS

OS ) 스레드 Thread

by 휴일이 2025. 1. 15.

스레드 Thread 구성

  • 스레드 ID
  • PC
  • 레지스터 집합
  • 스택

→ 스레드는 고유의 레지스터, 스택, 프로그램카운터를 가지고 있다.

멀티스레드 웹 서버 구조

  • 웹 서버가 다중 스레드화 되면, 서버는 클라이언트의 요청을 listen 하는 별도의 스레드를 생성한다.
    • 요청이 들어오면 다른 프로세스를 생성하는 게 아니라, 요청을 서비스 할 새로운 스레드를 생성 → 또는 스레드 풀에서 스레드 할당하여 요청 위임.
    • listen 하는 스레드는 요청을 listen 하는 작업을 재개한다.

노드랑은 다름

  • 노드는 메인 스레드가 요청을 수신하긴 하지만 I/O 를 Non-Blocking 한다.
  • 스레드 모델은 스레드를 사용해서 Blocking 처리 해도 상관 없음.(어차피 요청 받는 애랑 실행하는 애랑 따로니까)

→ 설계 철학 자체가 다름.

그럼 스프링도 마찬가지로 요청만 주고 받는 메인 스레드 역할이 있나?

Acceptor Thread (톰캣)

메인 스레드 역할. 요청을 “수신

  • 보통 수신 포트(80, 443) 마다 한 개 씩 둠.
  • 네트워크 소켓 요청을 수신하고 처리 큐에 추가.
  • 단순 수신 작업만 하기 때문에 얘를 늘려봤자 성능에 영향 없다고 해도 무방.

Worker Thread (톰캣)

실제로 요청을 받아 일해서 응답을 생성하는 스레드. “응답”

  • 실제 일하는 스레드는 얘들이라 얘네 갯수를 늘리는 게 성능에 도움 됨.

스프링 서버 요청 처리 예시

  1. 요청 수신.
    • 네트워크 요청이 들어오면 Acceptor Thread 가 감지해서 큐에 추가
  2. 스레드 풀에서 스레드 할당.
    • 스레드 사용하여 요청 처리.
  3. 요청 처리 완료.
    • 완료해서 응답을 생성 후, 다시 스레드 풀에 들어가서 유휴 상태(유후 상태 아님ㅎ)

멀티 스레드 프로그래밍의 장점

  1. 응답성 : 긴 작업을 수행하더라도 사용자에게 응답할 수 있음.
    • 예) 오래 걸리는 연산을 별도의 비동기 스레드에서 실행한다면, 응답에 문제 없음.
  2. 자원 공유 : 스레드는 자동으로 본인이 속한 프로세스의 자원과 메모리를 공유한다.
    • 프로세스는 공유 메모리나 메시지 전달 기법을 이용해야만 자원 공유 가능.
  3. 경제성 : 자원 공유가 되기 때문에 스레드를 생성하고 문맥 교환 하는 것이 더욱 더 경제적이다.
    • 스레드 간 컨텍스트 스위칭이 더 빠르고, 스레드 생성 비용도 프로세스 생성 비용보다 시간과 메모리를 덜 소비한다.
  4. 규모 적응성 : 멀티 프로세서 구조에서는 각 스레드가 다른 코어에서 병렬로 수행될 수 있음.
    • 단일 스레드 프로세스는 코어가 아무리 많아도 오직 한 코어에서만 실행 가능.
💡병행시스템 : 둘 이상의 작업 병행 가능. → 멀티 프로세서 활용
병렬시스템 : 둘 이상의 작업을 동시에 수행 가능. → 
멀티 스레드 활용

멀티 코어에서 프로그래밍을 하기 위해 유의해야 할 점 5가지

  1. 독립된 태스크로 나눌 영역을 찾아보자.
  2. 전체 작업에 균등한 기여도를 가지도록 태스크를 나누자.
  3. 태스크가 접근하고 조작하는 데이터 또한 개별 코어에서 사용할 수 있도록 나눠져야 한다.
  4. 접근하는 데이터에 둘 이상의 태스크 사이에 종속성이 없는가 ? 종속적인 경우에는 잘 동기화 하자.
  5. 멀티 코어에서 병렬 실행될 경우 여러가지 실행 경로(실행 결과)가 있을 수 있으니, 디버깅을 열심히 하자ㅎㅎ

병렬 실행 유형

  • 데이터 병렬 실행
    • 동일한 데이터의 부분 집합을 다수의 코어에 분배하고 각 코어에서 동일한 연산을 실행.
    • 예) 크기 N 인 배열을 [0] ~ [N / 2 -1] , [N/2] ~ [N-1] 까지 분배하여 실행.
  • 태스크 병렬 실행
    • 데이터가 아닌 태스크(스레드)를 다수의 코어에 분배.
    • 예) 두 개의 스레드를 실행하여 고유한 통계 연산 실행 → 이 경우 조건이나 매개변수를 통해 똑같지만 다른 작업을 하는 것처럼 느끼게 할 수도(배열을 똑같이 넣어도 매개변수를 다르게 해서 크기 N 인 배열을 [0] ~ [N / 2 -1] , [N/2] ~ [N-1] 까지 분배하여 실행. ← 이걸 똑같이 실행하는 거임
💡데이터와 태스크 병렬 처리는 상호 배타적이지 않다! 실제로 두 가지 전략을 혼합하여 사용한다.

 

다중 스레드 모델

  • 사용자 스레드는 커널 위에서 사용자 수준에서 관리하며 커널 지원 X
  • 커널 스레드는 운영체제에 의해 직접 지원되며 관리 됨.

다대일 Many-To-One Model

  • 많은 사용자 스레드를 하나의 커널 스레드로 매핑.
  • 스레드 관리는 사용자 공간의 스레드 라이브러리에서 진행되므로 효율적.
  • 단, Blocking-SystemCall 시에 전체 프로세스가 Blocking 된다.
    • Blocking 시스템콜 : 호출된 시스템콜이 완료될 때까지 호출한 프로세스가 대기 상태.

→ 다중 코어의 장점을 살리지 못함, 현재는 거의 존재하지 않음ㅎㅎ

일대일 모델 One-to-One

각 사용자 스레드를 각각 하나의 커널 스레드로 매핑. 하나의 스레드가 Blocking 시스템 콜을 호출하더라도 다른 스레드가 실행될 수 있음.

  • 멀티프로세서에서 멀티 스레드가 병렬로 수행되는 것을 허용하므로 최구!
  • 유일한 단점은 사용자 스레드를 만드려면 해당 커널 스레드를 만들어야되어서 많은 커널 스레드가 시스템 성능에 부담을 줄 수 있음.

다대다 모델 Many-to-Many

여러 개의 사용자 스레드를 더 작은 수, 또는 같은 수의 커널 스레드로 멀티플렉스 합니다. *멀티플렉스: 여러개를 하나로 묶어서 관리하는 것, 여기서는 여러 사용자 스레드를 하나의 커널 스레드로 묶어서 관리하거나 실행한다.

  • 개발자는 많은 스레드를 생성하고 커널 스레드가 이와 병렬로 수행 가능
  • Blocking 시스템 콜이 발생했을 때, 커널이 다른 스레드의 수행을 스케쥴링.
  • 한 사용자 스레드가 하나의 커널 스레드에만 연관 됨.
  • 근데 구현하기가 어렵고, 이젠 하드웨어 성능이 좋아져 코어 수가 증가해서 굳이 커널 스레드 수를 제한할 필요가 있나? → 그래서 일대일 모델이 주로 사용 됨.

스레드 풀 Thread Pool

프로세스를 실행할 때 일정한 수의 스레드를 미리 풀로 만들어 두고, 요청이 오면 대신 스레드 풀에 제출하고 추가 요청 대기 재개. 사용 가능한 스레드가 있으면 즉시 요청 실행 → 아니라면 작업 가능한 스레드가 생길 때까지 작업 대기.

다중 스레드 서버의 문제

  1. 스레드를 생성하는데에 소요되는 시간.
  2. 요청마다 새 스레드를 서비스 한다면 “한 시스템에서 동시에 실행할 수 있는 최대 스레드 수”가 몇 개까지 가능한지 한계 설정해야 됨.
    • 무한정 만들면 자원 고갈 ㅜㅜ.

→ 이런 문제들을 해결하기 위해 스레드풀 을 만들었다**.**^^

스레드 풀의 장점

  1. 기존 스레드를 사용하는 것이 종종 더 빠름.
  2. 스레드 개수에 제한을 두니까 많은 스레드를 병렬 처리할 수 없는 시스템에 오히려 좋아.
  3. 태스크를 생성하는 방법을 태스크로부터 분리하면 태스크를 실행을 다르게 할 수 있음
    • 예를 들어 태스크를 일정 시간 후에 실행하도록 스케쥴하거나 주기적으로 실행시키거나..

스레드 갯수 정하기

  • CPU 수
  • 물리 메모리 용량
  • 동시 요청 클라이언트 최대 개수
    • 정교하게 하려면 풀의 활용도를 보며 동적으로 풀 크기를 바꿔줄 수도 있다!
    • 부하가 적을 때에는 더 적은 메모리 소모량.

스레드 관련 이슈

Fork() 및 Exec() 시스템 콜

만일 한 프로세스의 스레드가 fork() 를 호출하면, 새로운 프로세스는..

  1. 모든 스레드를 복제해야 하는가?
  2. 한 개의 스레드만 가지는 프로세스여야 하는가?

→ 대부분 운영체제는 두 가지 버전 다 제공한다.

답 : 응용 프로그램에 달려있다.

  • fork() 후 바로 exec() 를 한다? 모든 스레드를 다 복제할 필요 없으니 fork() 시스템 콜을 호출한 스레드만 복사해주면 됨.
  • fork() 후 exec()하지 않는다? 모든 스레드를 복제하는 게 맞음.

signal handling

트랩, 타이머 만료 등 프로세스에 이벤트가 일어났음을 알려주기 위해 사용한다.

?: 프로세스가 여러 스레드를 가지고 있는 다중 스레드 프로그램에서 신호 처리는 어떻게 해야할까? 어떤 스레드에 신호를 전달해야 되나?

일반적인 선택

  • 신호가 적용될 스레드에게 전달.
  • 모든 스레드에 전달
  • 몇몇 스레드에만 선택적으로 전달
  • 특정 스레드가 모든 신호를 받도록 지정.

동기식 신호

특정 스레드나 프로세스 동작에 의해 직접 발생하는 신호

  • 신호를 발생시킨 스레드나 프로세스에만 신호가 전달됨.
  • 발생 시점이 명확하며 오류가 발생한 그 시점에 처리 됨.
  • 동작을 수행하던 스레드나 프로세스에 대한 신호라서 단일 컨텍스트에서 이루어짐.
  • 보통 프로세스 내에서 발생하며 외부 요인 없음

→ 예 ) 잘못된 메모리 접근, 0으로 나누기, 잘못된 명령…

비동기식 신호

외부 이벤트(타이머, 다른 프로세스 등)에 의해 발생

  • 어떤 스레드나 프로세스 등에서 처리될 수 있으며 처리 대상이 명확하지 않음.
  • 외부 요인에 의해 동작 흐름에 상관 없이 발생.
  • 운영체제 이벤트와 관련있으며, 특정 스레드가 처리하도록 할 수 있음.

→ 예 ) 키보드 인터럽트, 강제 종료 요청, 타이머…

동기식 신호는 신호를 야기한 스레드에만 전달되어야 하지만, 비동기식 신호는 명확하지 않다.

  • 프로세스를 kill 하는 함수는 pid 를 사용해 프로세스 자체를 강제 종료하지만
  • 스레드를 pthread_kill 하는 함수도 있음 → tid 로 스레드를 종료함.

스레드 취소

스레드가 끝나기 전에 강제 종료 시키는 작업.

  • 예1) DB 병렬 검색하고 있다가 그 중 한 스레드가 결과를 찾았다면 나머지 스레드는 취소되어도 됨.
  • 예2) 웹 브라우저에서 새로고침 등 취소 → stop 버튼 누르면 웹페이지를 가져오던 스레드 취소.

목적 스레드의 발생 두 가지 경우

목적스레드(취소되어야 할 스레드)

  1. 비동기식 취소 : 한 스레드가 즉시 목적 스레드 강제 종료.
    • 운영체제는 취소된 스레드로부터 모든 시스템 자원을 다 회수하지 못하는 경우가 있다.
    • 그래서 비동기식으로 스레드를 취소하면 필요한 시스템 자원을 모두 사용가능한 상태로 만들지 못할 수도 있다.
  2. 지연 취소 : 주기적으로 자신이 강제 종료 되어야 할지를 점검한다. → 이 경우 질서정연하게 강제종료 될 수 있음.
    • 한 스레드가 목적 스레드를 취소해야한다고 표시하지만, 실제 취소는 목적 스레드가 취소 여부를 결정하기 위한 플래그를 검사한 이후에야 일어남.
    • 자기가 취소되어도 안전하다고 판단되는 시점에서 취소 여부 검사.

지연 취소

  • pthread_join(tid, NULL)→ 스레드가 종료될 때까지 join 스레드가 대기하다가, 종료되면 join() 함수 종료, 반환.
  • pthread_cancel(tid) → 특정 스레드 취소하라는 요청.
  • pthread_exit() → 호출한 스레드 종료
728x90