[개념원리] Thread

봄도둑·2022년 8월 5일
0

개념원리

목록 보기
2/3

thread에 대한 내용을 공부할 때 정리한 글입니다. 잘못된 내용이 있거나 추가할 내용이 있으면 언제든 말씀 부탁드립니다.

1. 프로세스

운영체제에 의해 메모리 공간을 할당받아 CPU에서 실행, 제어되고 있는 프로그램 (독립적인 개체)

동적인 개념으로는 실행된 프로그램을 의미

  • 데이터(data) + 자원(memory) + 스레드(thread)로 구성
  • 프로그램
    • 파일 시스템에 존재하는 실행 파일
    • 실제 프로그램이 실행되기 까지는 스레드가 데이터 + 자원을 활용하여 작업을 수행
  • 프로세스는 각각 독립된 메모리 영역(code, data, stack, heap)을 할당 받음
  • 프로세스는 최소 1개의 스레드(메인 스레드)를 가지고 있음
  • 각각의 프로세스는 독립적 → 서로간 별도의 주소 공간에서 실행, 다른 프로세스의 변수나 자료구조에 접근할 수 없음
  • 프로세스가 다른 프로세스의 자원에 접근하려면 프로세스간의 통신(IPC)를 사용 ex) 파이프, 파일, 소켓등을 이용

2. 스레드

2-1. 개요

하나의 프로세스 내에서 여러 개의 실행 흐름(단일, 동시적, 병렬적)을 두어 작업을 효율적으로 처리하기 위한 모델

사전적 의미 : 프로세스 내에서 실행되는 여러 흐름의 단위, 프로세스의 특정한 수행 경로 → 프로세스가 할당받은 자원을 이용하는 실행의 단위

  • 스레드는 프로세스 내에서 각각의 stack만 따로 할당 받고 code, data, heap 영역은 공유
    • stack을 스레드마다 독립적으로 할당하는 이유
      • PC 값은 스레드가 명령을 어디까지 수행하였는지 나타냄
      • 스택 메모리 공간을 독립적으로 사용해야 독립적인 함수 호출이 가능 → 독립적인 실행 흐름이 가능해짐
    • PC Resister도 스레드마다 독립적으로 할당 → 스레드는 CPU를 할당 받았다가 스케줄러에 의해 다시 선점 → 명령이 연속적으로 수행되지 못하고 어디까지 수행됬는지 기억할 필요
  • 프로세스 내의 주소 공간이나 자원들(heap etc...)같은 프로세스 내의 스레드간 공유하면서 실행
  • 하나의 스레드가 프로세스 자원을 변경하면 이웃 스레드(같은 프로세스 내 다른 스레드)는 그 변경 결과를 즉시 확인 가능

(이미지 출처 : https://goodgid.github.io/What-is-Thread/)

(프로세스 내에서 스레드, 이미지 출처 : https://gmlwjd9405.github.io/2018/09/14/process-vs-thread.html)

2-2. 스레드의 생명 주기와 상태


(이미지 출처 : https://velog.io/@ruthetum/JAVA-Thread-Multitasking)

  • NEW : 스레드가 생성되었지만 아직 실행할 준비 X
  • RUNNABLE : 스레드가 JVM에 의해 실행되고 있거나 실행 준비되어 스케쥴링을 기다리고 있는 상태 → 이 녀석이 callable 상태와 많이 비교됨. 찾아보자
  • WAITING : 일시 정지 상태, 다른 스레드의 통지를 기다리는 상태, 일반적으로 스레드 동기화를 위해 사용
  • TIMED_WAITIMG : 스레드가 sleep(n)을 호출 → n ms동안 일시정지 상태(잠든 상태)
  • TERMINATED : 스레드 종료. run()이 끝나면 소멸
  • BLOCK : 일시정지 상태. 사용하려는 객체의 모니터 락(monitor lock)이 풀리기를 기다리는 상태 → 스레드가 I/O 작업 요청을 하면 JVM이 자동으로 BLOCK 상태로 만듦

2-3. 스레드 구분

2-3-1. 메인 스레드

main 메소드도 하나의 스레드를 갖는데 이를 메인 스레드라고 부름 → 메인 스레드는 프로그램이 시작하면 가장 먼저 실행되는 스레드로 모든 스레드는 메인 스레드로부터 생성

다른 스레드를 생성해서 실행하지 않는다면 메인 스레드가 종료되는 순간 프로그램도 종료

(이미지 출처 : https://www.geeksforgeeks.org/main-thread-java/?ref=lbp)

2-3-2. 데몬 스레드

다른 일반 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드

해당 스레드가 start() 되기 전 setDeamon(true)로 데몬 스레드 지정을 해야 데몬 스레드 사용 가능

데몬 스레드는 자기 자신이 종료되지 않아도 다른 실행중인 일반 스레드가 없다면 종료

  • 데몬 스레드를 사용하는 이유
    • 게임 어플리케이션이 하나 있음 → 이 어플리케이션의 메인 스레드는 종료 버튼을 누르면 끝남
    • 하지만 메인 스레드와 별개로 다른 일반 스레드인 자동 저장 스레드가 있음 → 일정 주기마다 자동 저장을 하게 될 경우 아무리 종료 버튼을 눌러 메인 스레드를 종료시켜도 자동 저장 스레드가 죽지도 않고 계속 살아 있기 때문에 이 게임은 절대 꺼지지 않게 됨
    • 이러한 상황에서 메인 스레드가 종료될 때 함께 종료되는 데몬 스레드로 자동 저장 스레드를 만들면 종료 버튼을 누를 때 같이 끝나게 될 수 있음

2-3-3. 스레드 그룹

일반 스레드를 생성할 때 스레드를 집합 단위로 관리할 수 있음 → 이게 스레드 그룹

모든 스레드는 반드시 하나의 스레드 그룹에 속함

스레드 그룹을 지정해주지 않으면 기본값으로 main 스레드 그룹에 속하게 됨 → 스레드는 자신을 생성한 부모 스레드의 그룹과 우선순위를 상속 받기 때문

(이미지 출처 : https://skyjumps.tistory.com/?page=69)


3. 멀티 스레드

3-1. 개요

하나의 프로세스를 여러 스레드로 구성, 각 스레드로 하여금 하나의 작업을 처리하도록 하는 것

→ 어려운 말로 하자면 하나의 프로세스를 다수의 실행 단위로 구분하여 자원을 공유하고 자원의 생성과 관리의 중복성을 최소화 하여 수행 능력을 향상 시키는 것

  • 보다 쉬운 예시 : 스타크래프트의 캐리어를 생각해보자.
    • 캐리어 : 프로세스, 인터셉터 : 스레드
    • 프로세스가 여러 스레드를 태우고 일을 처리하는 형태
    • 캐리어가 터지면 인터셉터들도 다 터짐 → 하나의 프로세스가 끝나면 스레드가 일을 하고 있어도 끝나버림
    • 하나의 프로세스가 병렬 처리를 위해 작은 단위의 스레드를 돌리는 것이라고 생각하자

(인터셉터 예제로 이해는 가긴 가는데....) → 하나의 프로그램에서 여러 개의 일을 수행할 수 있도록 해주는 것

3-2. 멀티 스레드 사용 이유

멀티 프로세스를 이용하여 동시에 처리하던 일을 멀티 스레드로 구현할 경우 메모리 공간과 시스템 자원 소모가 줄어들게 됨

  • 잠깐 멀티 프로세스는 또?????
    • 마찬가지로 하나의 응용 프로그램을 여러 개의 프로세스로 구성한 것 → 병렬 처리된 게 프로세스냐 스레드냐 차이 (물론 큰 차이가 존재하긴 하지만 이해 하는 차원에서...)
  • 이쯤에서 짚어보자 : 멀티 프로세스와 멀티 스레드의 차이
    • 멀티 프로세스는 data, heap, stack 영역 모두 비공유
    • 멀티 스레드는 stack만 비공유 → data와 heap을 공유하고 있다는 것은 장점이 되면서 동시에 멀티 스레드의 약점으로 작용
    • 멀티 스레드는 프로세스를 생성하여 자원을 할당하는 과정도 줄어들 뿐만 아니라 콘텍스트 스위칭하는 것보다 오버헤드를 줄일 수 있음(으음....?? 콘텍스트 스위칭 글 : https://beststar-1.tistory.com/26 → 스케줄러가 기존 실행 프로세스를 우선 순위 때문에 미뤄버리고 새 프로세스로 교체해야 할 때 프로세스 상태 값을 교체하는 작업) ⇒ 프로세스간의 컨텍스트 스위칭은 CPU 레지스터 교체 뿐만 아니라 RAM과 CPU 사이의 캐쉬 메모리에 대한 데이터 까지 초기화(쉽게 말해 오버헤드가 큼)
    • 멀티 스레드간 heap과 data영역은 공유되기 때문에 스레드간의 통신 비용이 훨씬 작아짐

한마디로 멀티 프로세스 기반보다 더 적은 자원과 시간을 들여서 처리할 수 있다는 것

3-3. 멀티 스레드의 문제점

반복되는 이야기지만 멀티 스레드는 스레드들간 heap과 data 영역을 공유 → 어떤 스레드가 다른 스레드에서 사용중인 변수나 자료 구조에 접근하여 엉뚱한 값을 읽어오거나 수정할 수 있음

서로 간섭할 수 있는 가능성으로 설계와 디버깅이 까다로움

프로세스 밖에서 동작하고 있는 스레드를 제어할 수 없음

치명적인 문제 중 하나로 병렬 처리된 스레드 하나에 문제가 발생하면 프로세스 전체에 영향을 줌

(이미지 출처 : https://www.crocus.co.kr/1510)

heap 영역과 stack 영역의 공유로 발생하는 문제를 해결하기 위해 동기화 작업을 진행

  • 그러나 이러한 동기화 작업은 스레드들간 작업 처리 순서를 컨트롤 하고 공유 자원에 대한 접근을 컨트롤함
  • 이로 인해 병목 현상이 발생 → 동기화를 할 때에는 메소드 전체를 동기화 할 것인지, 특정 부분만 동기화 할 것인지 성능에 대한 고민 필요

4. 스레드 동기화

4-1. 개요

여러 스레드가 하나의 리소스를 사용하려 할 때 사용하려는 스레드를 제외한 나머지 스레드는 접근하지 못하게 막는 것

  • 공유 자원 접근 순서에 따라 실행 결과가 달라지는 프로그램의 영역을 임계 구역이라고 부름 → 수비게 말해 둘 이상의 스레드가 동시에 접근해서는 안되는 공유 자원에 한 번에 하나의 스레드만 이용하게끔 보장해주는 영역

임계 구역 조건

  • 상호 배제 : 한 스레드가 임계 구역에 들어가면 다른 스레드는 임계 구역에 들어갈 수 없음
  • 한정 대기 : 하나의 스레드가 계속 자원을 사용하고 있어 다른 스레드가 사용하지 못한 채 무한정 기다리면 안됨 → 무한 대기하면 안됨. 특정 스레드가 임계 구역에 진입하지 못하면 안됨
  • 진행의 융통성 : 한 스레드가 다른 스레드의 작업을 방해해서는 안됨

4-2. 스레드 동기화 방법의 종류

mutex, semaphore, monitor → 운영체제의 동기화 기법

  • 뮤텍스(mutex)

    • 스레드의 동시 접근을 허용하지 않음

    • 뮤텍스 동기화는 임계 영역에 들어가기 위해 뮤텍스를 들고 있어야 함 → 임계 영역에 들어간 스레드가 뮤텍스를 이용해 내부에서 다른 스레드가 못 들어오게 잠금

    • 변기가 1개인 화장실

      (이미지 출처 : 나)

  • 세마포어(semaphore)

    • 뮤텍스와 비슷하지만 접근 순서 동기화임

    • 변기가 여러개인 화장실 → 다른 변기가 비면 그 자리에 들어갈 수 있음

      (이미지 출처 : 나)

  • 모니터(monitor)

    • 뮤텍스와 상태값(condition variables, queue)를 가지고 있는 동기화 메커니즘
    • 모니터는 하나의 프로세스 내에 다른 쓰레드 간에 동기화 할 때 사용 → 뮤텍스는 다른 프로세스간 동기화할 때 사용
    • 자바에서는 모니터를 모든 객체가 사용할 수 있도록 기본적으로 제공
    • 세마포어는 카운터라는 변수값으로 개발자가 매번 값을 따로 지정해줘야 함 → 반면 모니터는 이러한 일들이 캡슐화가 되어 있어서 카운터 값을 줄 필요 없이 synchronized(), wait(), notify() 등의 키워드를 이용해 좀 더 편하게 동기화 가능
    • syncronized : 메소드 전체 혹은 특정 영역을 임계 구역으로 지정 → 임계 구역을 최소화 할 수 있도록 해야함
    • wait() : 동기화된 임계 영역의 코드를 수행하다가 작업을 더 이상 진행할 상황이 아니라면 wait을 호출해 스레드가 락을 반납하고 기다리게 함
    • notify() : 다시 진행할 수 있는 상황이 오면 notify()를 호출해 작업을 중단한 스레드가 다시 락을 얻음 → 이 때 가장 오래 기다린 스레드가 락을 얻을 수 있다는 보장이 없고 스레드가 오래 기다리는 기아 현상이 발생 → notiyAll()을 사용하면 되나 이 경우 모든 스레드가 연락을 받고 필요하지도 않은 lock을 얻기 위해 경쟁 상태에 들어가는데 이것도 방지하기 위해 각 스레드를 구별해서 연락하는 것이 필요

(이미지 출처 : https://velog.io/@ruthetum/JAVA-Thread-Multitasking, 명품 JAVA 프로그래밍 (황기태, 김효수 저))

4-3. 데드락

두 개 이상의 작업이 서로 상대방의 작업이 끝나기를 기다리기만 하고 작업이 완료되지 못하는 상태

둘 이상의 스레드가 lcok을 획득하기 위해 대기중 → 이 때 해당 lock을 보유한 다른 스레드 역시 또 다른 lock을 기다리며 block 상태에 놓이는 것 → 즉 다수의 스레드가 같은 lock을 동시에 다른 명령에 의해 획득하려 할 때 발생

  • 데드락 발생 원인
    • 상호 배제 : 자원이 임계구역에서 보호되고 있다면 스레드끼리 동시에 사용할 수 없고 데드락 상태에 빠짐
    • 비선점 : 자원을 얻기 위해 다른 스레드가 해당 자원을 놓아줄 때까지 기다리며 데드락 상태에 빠짐
    • 점유와 대기 : 이미 자원 하나를 사용하고 있는 중에 다른 자원을 기다리게 되면 소유중인 자원을 기다리는 스레드가 발생하고 데드락에 빠짐
    • 원형 대기 : 자원을 요구하는 방향이 원을 이루면 (둥글게 둥글게) 서로 자원에 대한 양보를 하지 않기 때문에 데드락에 빠짐

*REFERENCE

https://irerin07.tistory.com/151
https://honbabzone.com/java/java-thread/#step-6--스레드-그룹
https://www.crocus.co.kr/1510
https://velog.io/@ruthetum/JAVA-Thread-Multitasking
https://goodgid.github.io/What-is-Multi-Thread/#멀티-프로세스와-멀티-쓰레드의-차이점
http://www.tcpschool.com/java/java_thread_concept
https://gmlwjd9405.github.io/2018/09/14/process-vs-thread.html
https://goodgid.github.io/What-is-Thread/
https://beststar-1.tistory.com/26
https://beststar-1.tistory.com/6

profile
Java Spring 백엔드 개발자입니다. java 외에도 다양하고 흥미로운 언어와 프레임워크를 학습하는 것을 좋아합니다.

0개의 댓글