ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Thread 알아보기
    System 2022. 12. 13. 19:09
    728x90
    반응형

    - 목차

     

     

    같이 읽으면 좋은 글

    https://westlife0615.tistory.com/285

     

    Call Stack 이해하기

    - 목차 * 소개 어떤 프로그램을 사용한다는 의미에 대해서 생각해 볼 필요가 있습니다. 예를 들어, 크롬같은 웹 브라우저를 사용한다거나 Spring 같은 서버를 실행시킨다거나 하는 행동들을 말이

    westlife0615.tistory.com

     

    소개.

    Thread 는 프로그램의 실질적인 실행 단위입니다.
    소위 프로그램이 실행되면, 실행된 프로그램을 Process 라고 하죠.

    Thread 는 이 실행 중인 Process 내부에서 다루어지는 가장 작은 실행 단위입니다.

    실제로 CPU 를 차지해서 코드들을 순차적으로 실행하는 것이 Thread 입니다.

     

    Thread 는 Task Scheduler (or Thread Scheduler) 에 의해서 Time-Shared 방식으로 CPU 를 점유합니다.

    Task Scheduler 는 OS 의 구성요소로써 Thread 들의 Scheduling 을 관리합니다.

    앞으로 알아보겠지만, Task Scheduler 이 Thread 의 우선순위와 상태를 고려해서 어떤 Thread 가 CPU를 차지할지 결정합니다.

    그리고 Thread 가 실행되기에 앞서 Thread 는 Process 에 의해서 생성된 자식같은 존재이기 때문에

    Process Scheduler 에 의해서 해당 Process 가 Running 상태가 되어야합니다.

    그래서 순서적으로 Process 가 Process Scheduler 에 의해서 Running 상태가 되고,

    Running Process 의 Thread 들이 Task Scheduler 에 의해서 Running 상태가 됩니다.

    즉, multi-process & multi-threaded 환경에서 Process Scheduler 와 Thread Scheduler 가 유기적으로 동작해야합니다.

     

    Thread 에 대해서 상세하게 알아보는 시간을 가지겠습니다.

     

    Main Thread.

    Main Thread 는 모든 프로세스가 하나 이상 가지는 기본적인 Thread 입니다.

    보통 프로그램의 Entry Point 라고 부르는 main function 이 Main Thread 에 해당합니다.

    main function 에 작성된 여러 코드 라인들. 즉 main function 에 작성된 실행 흐름들이 Main Thread 에 의해서 실행됩니다.

     

    보통 HTTP 서버들의 (Spring 이나 gin-gonic 같은) Main Thread 를 예시로 들어보면,

    Main Thread 에서 서버의 configuration 을 설정하고, tcp port 를 listening 하는 infinite loop 을 돕니다.

    계속 HTTP request 를 체크하죠.

    MySQL 같은 환경에서의 Main Thread 또한 이벤트 루프를 생성하여, MySQL 내부에서 발생하는 여러 이벤트를 수집하여 대처합니다.

     

    이처럼 Main Thread 는 쉬게 멈추지 않게 설계하여 다른 Worker Thread IO Thread 들과 통신하게 됩니다.

     

    프로그래밍 언어나 환경별로 다르겠지만,

    Main Thread 가 종료되면 해당 Process 가 종료되는 케이스들이 많이 있습니다.

    따라서 Main Thread 와 Worker Thread 의 설계 또한 중요해집니다.

     

    What is Core.

    Core 는 CPU 의 구성요소입니다.

    Thread 나 Process 가 CPU 를 점유한다는 의미는

    CPU 내부의 ALU (Arithmetic Logic Unit) 와 레지스터들을 점유하는 느낌인데요.

    ALU 는 프로그램의 Instruction 들을 처리할 수 있는 Circuit 들을 가지고 있습니다.

    연산을 위한 Adder 라든지, 부동소수를 처리하기 위한 Floating-Number Unit 과 연산의 결과를 저장하기 위한 레지스터들 있고,

    Core 는 이러한 코드 실행을 위한 중요 요소들을 가집니다.

    그래서 Core 의 수만큼 병렬처리가 가능해집니다.

     

    Task Scheduler 에 의해서 Thread 의 Core 할당이 이뤄지는데요.

    어떤 연산도 처리하고 있지 않은 Core 가 발견된다면 Task Scheduler 에 의해서 특정 Thread 들은 Core 에 할당됩니다.

     

     

    Thread Control Block (TCB).

     
    TCB 는 Thread 의 Execution Context (실행상태) 를 저장하는 자료구조입니다.
    Context Switching 이라는 표현을 많이 쓰죠 ?

    여기서 말하는 Context 들이 TCB 에 저장됩니다.

    즉, CPU 관점의 문맥 (Context) 를 뜻하는데요.

    Time-Shared 방식으로 여러 Thread 들이 CPU 를 점유하다보니

    Context Switching 에 의해서 CPU 를 재점유할 때, 과거의 실행 상태를 복원할 필요가 있습니다.

     

    - 마지막으로 실행한 코드가 무엇인지 (Program Counter)

    - 마지막으로 저장한 데이터들이 무엇인지 (Register, Stack Pointer)

    등이 식별할 수 있는 데이터들을 TCB 에 저장하여 새롭게 CPU 를 점유할 때 매끄럽게 동작할 수 있습니다.

     

    개별적인 요소에 대해서 알아보겠습니다.

    Parent Process.

    모든 Thread 는 태생적으로 하나의 Process 에서 파생됩니다.

    Parent Process 는 자신을 만든 Process 의 id 가 저장됩니다.

    반대로 Process 또한 자신이 생성한 Thread 의 정보를 가지고 있습니다.
     

     

    Thread ID.

    Thread 자신의 ID 입니다.


    Program Counter.

    현재 실행 중인 코드의 Memory 상 주소입니다.

    Program Counter 는 실행할 Code 의 메모리 주소를 가리키는데요.

    Program Counter 가 저장되지 않으면 다음번 실행 시점에 매끄러운 실행 흐름을 유지하기 불가능합니다.

    a++

    a = a + 1 과 같은 Increment 로직을 실행 중이거나

    while, for loop 를 실행 중인 상태에서 Program Counter 의 제대로 저장되거나 복원되지 않으면 프로그램의 결과는 기대한 결과와 달라집니다.


    Registers.

    eax, ebx, ecx, edx 처럼 연산을 위해서 사용되는 레지스터들이 있습니다.

    보통 mov eax, 1 처럼 데이터들을 레지스터에 임시로 저장하게 되는데요.

    이 값들 또한 TCB 에 제대로 저장이 안되다면 문제가 되겠죠?

     

    Stack Pointer.

    Stack Pointer 는 Call Stack 이 push, pop 되면서 Call Stack 최상단 값을 저장하는 레지스터입니다.

    Stack Pointer 은 Stack Frame 의 생성에 관여하는데요.

    새로운 함수가 호출될 때마다 Stack Pointer 를 통해서 새로운 함수의 Base Pointer 를 생성합니다.

     

    < Stack Pointer 에 대한 글을 같이 첨부합니다. >

    https://westlife0615.tistory.com/285

     

    Call Stack 이해하기

    - 목차 * 소개 어떤 프로그램을 사용한다는 의미에 대해서 생각해 볼 필요가 있습니다. 예를 들어, 크롬같은 웹 브라우저를 사용한다거나 Spring 같은 서버를 실행시킨다거나 하는 행동들을 말이

    westlife0615.tistory.com

     


    Thread State.

    Thread 의 상태는 여러가지가 있습니다.

    각 상태에 대한 설명과 예시를 알아보도록 하겠습니다.

     

    1. New.

    new 는 새롭게 생성된 Thread 입니다.

    아래 코드처럼 생성을 마친 상태입니다.

    Thread newThread = new Thread("newly-created-thread");

     

    2. Runnable, Ready.

     

    Runnable 은 시작된 Thread 입니다.

    다만 Scheduler 에 의해서 실질적인 시작 상태는 아닙니다.

    언제든지 Running 상태가 될 수 있는 상태입니다.

     

    java 에서 start 를 끝마친 Thread 가 Runnable 상태가 됩니다.

    Thread runableThread = new Thread("runnable-thread") {
          @Override
          public void run() {
          }
    };
    
    runableThread.start();

     

    3. Running.

    Running 상태의 Thread 는 CPU 에 의해서 실행되고 있는 상태입니다.

    아래 이미지는 visualVM 으로 Java 프로그램을 profiling 하는 이미지입니다.

    아래 이미지를 보면 main 이라는 Thread 가 초록색, 보라색 progress bar 를 가집니다.

     

    프로세스 실행 후 10초 동안만 Running 상태를 유지하고 나머지는 Sleep 으로 변경됩니다.

    10초로 설정했지만, visualVM 에는 Running time 이 8초로 나오는군요.

     

    <Running 상태에서 10초 이후에 Sleep 으로 변경>

    public static void main(String[] args) throws InterruptedException {
        Instant tenSecondsAfter = Instant.now().plus(10, ChronoUnit.SECONDS);
        
        while (true) {
          if (Instant.now().isAfter(tenSecondsAfter)) {
            Thread.currentThread().sleep(10000000L);
          }
        }
    }

     

    4. Blocked/Waiting.

    Blocked/Waiting Thread 는 다른 Thread 와의 상호작용을 위해서 Blocked 된 상태입니다.

    예를 들어,

    어떤 전역 변수를 두 Thread 가 사용할 때 Synchronization 을 위해서 모든 Thread 들은 특정 전역 변수를 선점한 Thread 를 Waiting 해야합니다.

    그 이후에 선점한 자원을 release 한 후에 다른 Thread 들이 해당 자원을 사용할 수 있습니다.

    또 다른 예로 network IO 작업의 경우에 network IO 를 수행하는 IO Thread 의 작업이 종료되어야

    네트워크 응답을 사용할 수 있게 됩니다.

    여러가지 이유로 Thread 는 Blocked / Waiting 상태가 될 수 있습니다.

     

    아래 이미지들은 Blocked 상태의 Thread 입니다.

     

    3개의 Thread 가 존재합니다.

    - 01-wait-after-10-seconds-thread

    - 02-wait-after-10-seconds-run-after-20-seconds-thread

    - main

     

    01-wait-after-10-seconds-thread 은 10초 동안 Running 상태에서 10 ~ 20초 동안 Blocked 상태로 변경되고,

    그 이후에 Running 상태로 변경되는 Thread 입니다.

     

    02-wait-after-10-seconds-run-after-20-seconds-thread 는

    01-wait-after-10-seconds-thread 의 Blocked 상태를 풀어주는 Thread 입니다.

    그리고 종료됩니다.

    <관련 로직>

    아래의 예제는 sharedObject 를 공유하는 두 Thread 간의 Synchronization 과 그에 따른 Blocked 상태를 구현하였습니다.

    public static void main(String[] args) throws InterruptedException {
        Object sharedObject = new Object();
    
        Thread newThread2 = new Thread("01-wait-after-10-seconds-thread") {
    
          Instant tenSecondsAfter = Instant.now().plus(10, ChronoUnit.SECONDS);
          @Override
          public void run() {
            while (Instant.now().isBefore(tenSecondsAfter)) {
            }
            synchronized (sharedObject) {
              try {
                sharedObject.wait(); // The thread waits until it's notified.
              } catch (InterruptedException e) {
                // Handle interruption if needed.
              }
            }
            while (true) {
            }
          }
        };
        newThread2.start();
    
        Thread newThread3 = new Thread("02-wait-after-10-seconds-run-after-20-seconds-thread") {
    
          Instant twentySecondsAfter = Instant.now().plus(20, ChronoUnit.SECONDS);
          @Override
          public void run() {
            while (Instant.now().isBefore(twentySecondsAfter)) {
            }
            synchronized (sharedObject) {
              sharedObject.notify(); // The thread waits until it's notified.
            }
          }
        };
        newThread3.start();
    
        Instant tenSecondsAfter = Instant.now().plus(10, ChronoUnit.SECONDS);
        while (true) {
          if (Instant.now().isAfter(tenSecondsAfter)) {
            Thread.currentThread().sleep(10000000L);
          }
        }
      }

     

    5. Terminated.

    Thread 가 종료된 상태입니다.

    종료된 Thread 와 관련된 리소스들은 제거 대상이 되며, TCB, PCB 에 기록된 데이터들이 제거됩니다.

    다만 즉시 제거되진 않고 실행 환경에 따른 Clean up 스케줄을 따릅니다.

     

    Call Stack In Thread.

    Thread 별로 Call Stack 이 생성됩니다.

    Heap 메모리의 경우에는 모든 Thread 들이 Process 의 Heap 메모리를 공유합니다.

    하지만 Stack 메모리의 경우에는 Thread 별로 생성됩니다.

    Thread 는 자신이 사용중인 레지스터를 기록하는데, 이때 base pointer 를 저장하는 ebp 레지스터 또한 저장됩니다.

    ebp 의 값에 따라 Thread 자신의 Call Stack 을 사용할 수 있습니다.

     

     

    추후에 작성할 내용들

    Priority.
    Thread Queue.
    Thread Scheduling.
    Task Scheduler
    Thread Safety.
    Thread-Specific Registers.

    반응형

    'System' 카테고리의 다른 글

    IPC Signal 알아보기  (0) 2023.10.07
    Shared memory communication 알아보기  (0) 2023.10.07
    [memory management] page 알아보기  (0) 2023.09.22
    Call Stack 이해하기  (0) 2023.09.21
    리눅스 프로세스  (0) 2023.01.24
Designed by Tistory.