본문 바로가기
CS/운영체제

Thread의 이해

by snow_white 2023. 3. 3.

Thread

  • CPU 이용의 기본 단위
  • 스레드 ID, 프로그램 카운터(PC), 레지스터 집합, 그리고 스택으로 구성
  • 같은 프로세스에 속한 다른 스레드와 코드, 데이터 섹션, 열린 파일이나 신호와 같은 운영체제 자원들을 공유

 

싱글 스레드

싱글 스레드의 특징

  • 하나의 프로세스에서 하나의 스레드 실행

싱글 스레드의 장점

  • 공유자원을 접근하는 동기화 문제를 신경쓰지 않아도 된다.
  • context switch 작업을 요구하지 않아서, 전환 비용이 들지 않는다.
  • 두 개의 작업에 대해 두 개의 스레드를 할당하여 작업할 경우에도 CPU를 선점하는 과정에서 context switch가 발생하는데, 단일 스레드에 비해 비용이 증가할 수 있다.

싱글 스레드의 단점

  • 여러 개의 CPU를 활용하지 못한다.

 

멀티 스레드

멀티 스레드의 특징

  • 하나의 프로세스에서 다수의 스레드 실행
  • 프로세스 내에서 자원을 공유하여 자원 생성과 관리 중복을 최소화
  • 서버가 많은 요청을 효율적으로 수행할 수 있는 환경 제공
  • 각각의 스레드가 고유의 레지스터와 스택으로 표현된다

 

멀티스레드 장점

  • 응답성(Responsiveness) : 유저 인터페이스를 처리할 때 블록될 필요 없이, excution을 계속 실행할 수 있다.
  • 자원 공유(Resource sharing) : 프로세스는 공유 메모리와 메시지 전달 기법으로 자원을 공유할 수 있다. 그러나 스레드는 자동으로 그들이 속한 프로세스 자원과 메모리를 공유할 수 있다. 프로세스 내에서 여러 개의 작업을 해야하는 스레드가 가지는 강점이다.
  • 경제성(Economy) : 프로세스 생성을 위해 메모리와 자원을 할당하는 것은 비용이 많이 든다. 스레드는 자신이 속한 프로세스의 자원을 공유하기 때문에 스레드를 생성하고 문맥 교환하는 일이 보다 경제적이다.
  • 규모 적응성(Scalability) : 다중 스레드의 이점은 다중 처리기 구조에서 더욱 증가할 수 있다. 다중 처리기 구조에서는 각각의 스레드가 다른 처리기에서 병렬로 수행될 수 있다. 단일 스레드 프로세스는 처리기가 아무리 많더라도 오직 한 처리기에서만 실행된다.

 

멀티 스레드의 단점

  • 하나의 스레드만 실행중일 때는 실행시간이 오히려 지연될 수 있다.
  • 멀티 스레딩을 위해 운영체제의 지원이 필요하다.
  • 스레드 스케쥴링을 신경써야 한다.

 

자바에서 Thread 사용하기

1. Thread 클래스 상속받기

Thread 클래스를 상속 받아 public viod run() 을 오버라이딩한다.

class MyThread1 extends Thread {
   public void run() {
      try {
         while(true) {
            System.out.println("thread is running...");
            Thread.sleep(500); // 0.5초마다 실행
         }
      } catch (InterruptedException ie) {
         System.out.println("interrupted");
      }
   }
}
public class ThreadExample1 {
   public static final void main(String[] args) {
      MyThread1 thread = new MyThread1();
      thread.start();
      System.out.println("start"); // main Thread 작업 먼저 수행 후 context switch
   }
}

 

2. Runnable 인터페이스 구현하기

자바는 다중 상속이 안 되기 때문에 Thread를 상속 받은 클래스는 다른 조상 클래스를 상속 받을 수 없기 때문에 Runnable이라는 인터페이스를 implements하여 public viod run() 을 오버라이딩한다.

class MyThread2 implements Runnable {
   public void run() {
      try {
         while(true) {
            System.out.println("thread is running...");
            Thread.sleep(500);
         }
      } catch (InterruptedException ie) {
         System.out.println("interrupted");
      }
   }
}

public class ThreadExample2 {
   public static final void main(String[] args) {
      Thread thread = new Thread(new MyThread2());
      thread.start();
      System.out.println("start");
   }
}

 

3. Runnable 람다 표현식 사용하기(JAVA 1.8 ~)

클래스를 만들기 귀찮다면, 람다 형식, 익명 스레드를 구현한다.

public class ThreadExample2 {
   public static final void main(String[] args) {
      Runnable task = () -> {
         try {
            while(true) {
               System.out.println("thread is running...");
               Thread.sleep(500);
            }
         } catch (InterruptedException ie) {
            System.out.println("interrupted");
         }
      }
      Thread thread = new Thread(task);
      thread.start();
      System.out.println("start");
   }
}

 

부모 스레드의 대기 시에는 join() 사용

public class ThreadExample2 {
   public static final void main(String[] args) {
      Runnable task = () -> {
         try {
            while(true) {
               System.out.println("thread is running...");
               Thread.sleep(500);
            }
         } catch (InterruptedException ie) {
            System.out.println("interrupted");
         }
      }
      Thread thread = new Thread(task);
      thread.start();

      try {
      	 // 해당 스레드가 멈출때까지 멈춤
         thread.join();
      } catch (InterruptedException ie) {
         System.out.println("Parent thread is interrupted");
      }
      System.out.println("start");
   }
}

join() 메소드는 실행중인 스레드를 강제로 실행 대기(lock) 상태로 변하게 한 뒤 특정 쓰레드가 실행되고 종료 될 때까지 기다리게 할 수 있다.

 

다중 코어 프로그래밍 Multicore Programming

  • 병행성과 병렬성
    • 병행성 (Concurrency) : 시분할과 같은 방법으로 동시에 실행하는 것처럼 보임
    • 병렬성 (Parallelism) : 실제로 동시에 실행됨.
  • 프로그래밍 도전과제
    1. 태스크 인식 (Identifying tasks) : task를 나눌 수 있는 영역을 찾는 것, task는 독립적이고 개별 코어에서 병렬 실행될 수 있어야 한다.
    2. 균형 (Balance) : 전체 작업에 균등한 기여도를 가지도록 task를 나누어야 한다.
    3. 데이터 분리 (Data splitting) : 개별 코어에 실행될 데이터를 나뉘어야 한다
    4. 데이터 종속성 (Data dependancy) : 둘 이상의 태스크가 실행될 때 데이터가 종속적인 경우 데이터의 동기화도 필요하다.
    5. 시험 및 디버깅 (Testing and debugging) : 다양한 실행 경로를 테스트, 디버깅해야 하기에 싱글스레드보다 어렵다.
  • 병렬 실행 유형
    • 데이터 병렬 실행 data parallelism : 동일한 데이터의 부분집합을 다수의 코어에 분배한 뒤 동일 연산을 실행하는데 초점
    • 태스크 병렬 실행 task parallelism : 데이터가 아닌 테스크를 다수의 코어에 분배하여 각자 고유위 연산을 실행.

 

암달의 법칙 (Amdahl’s Law)

순차 실행 구성요소와 병렬 실행 구성요소로 이루어진 app 에서 코어를 증가시켰을 때 성능 이득을 계산한다.

speedup <= 1/(S+((1-S)/N))
S = 시스템의 순차적으로 실행되는 요소 (serial)
N = 코어의 개수

 

다중 스레드 모델 Multithreading Models

  • 사용자 스레드(user threads) : 커널 서포트 없이 유저 모드 위에서 동작하는 스레드
  • 커널 스레드(kernel threads) : OS가 직접 관리하는 스레드

 

다대일 모델

많은 유저 스레드를 하나의 커널 스레드로 연결한다. 스레드 관리가 유저 스레드에 의해 행해지기에 효율적이나, 한 스레드가 봉쇄형 시스템 콜을 행할 경우 전체 프로세스가 봉쇄된다. 또한 다중 스레드가 다중 코어 시스템에서 병렬로 실행될 수 없다. 그래서 현재 이 모델은 거의 사용하고 있지 않다.

 

일대일 모델

각 사용자 스레드가 각각 하나의 커널 스레드로 연결된다. 병렬성을 제공하지만 사용자 스레드 생성에 각각 커널 스레드가 필요하기에 많은 커널 스레드가 시스템에 부담을 줄 수 있다.

 

다대다 모델

이 모델은 위 문제를 어느 정도 해결했다. 필요한 만큼 사용자 스레드를 생성하고, 상응하는 커널 스레드가 다중 처리기에서 병렬로 수행될 수 있다. 스레드가 block 시스템 콜을 발생하면 커널이 다른 스레드의 수행을 스케줄 할 수 있다. 이 모델의 경우에도 두 수준 모델(two -level model) 로 일대일 연결도 지원한다. 그러나 다대다 모델은 구현의 어려움, 코어 수의 증가로 스레드 제한의 중요성이 줄어들어 일대일 모델을 많이 사용한다.

 

스레드 라이브러리

프로그래머들에게 스레드를 생성하고 관리하기 위한 API를 제공한다. 사용자 수준 라이브러리, 커널 수준 라이브러리를 제공하는 방법이 있다. Pthread, Windows 및 Java 세 종류의 라이브러리라 주로 사용된다.

  • POSIX Pthreads
  • Windows thread
  • Java thread : JVM, 운영체제에 종속적

 

Pthreads

POSIX가 스레드 생성과 동기화를 위해 제정한 표준 API이다. 동작에 따른 정의를 명세할 뿐 구현은 아니다. Linux, MaxOS 등의 시스템에서 Pthreads 명세를 구현하고 있다. 아래는 하나의 스레드를 생성하는 예제이다.

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

int sum; /* this data is shared by the thread(s) */
void *runner(void *param); /* threads call this function */
int main(int argc, char *argv[])
{
    pthread_t tid; /* the thread identifier */
    pthread_attr_t attr; /* set of thread attributes */
    /* 어트리뷰트 지정  */
    pthread_attr_init(&attr);
    /* 스레드 생성 */
    pthread_create(&tid, &attr, runner, argv[1]); // 런너 설정, 명령어의 arg 입력,  Java의 new Thread 역할
    /* 스레드가 끝날때까지 대기 */
    pthread_join(tid, NULL);
    printf("sum = %d\\n",sum); // runner가 다 돌고 나면 출력됨
}

/* The thread will execute in this function */
void *runner(void *param) // Java의 public void run()의 역할
{
    int i, upper = atoi(param);
    sum = 0;
    for (i = 1; i <= upper; i++)
        sum += i;
    pthread_exit(0); // 스레드 종료
}

 

'CS > 운영체제' 카테고리의 다른 글

운영체제의 개념과 구조  (0) 2023.02.05

댓글