제출하지 못했던 10주차까지 완료!

 

프로세스?

스레드 단위 작업을 지원하기 위한 자원의 할당 단위이며, 커널 위에서 현재 실행 중인 프로그램을 의미한다.

 

프로세스 안에는 하나 이상의 스레드가 존재한다.

 

 

왜 멀티 프로세스 방식을 사용하지 않는가?

프로세스를 이용하여서도 멀티 프로세싱이라고 하는 방법을 이용하여 하나의 작업을 병렬적으로 처리할 수 있다. 하지만 이 방식은 각각의 프로세스가 쉽게 공유되지 않는 자신만의 데이터 영역들을 가지고 있기 때문에 데이터 처리, 공유 방식과 메모리 공간 점유에 대한 문제가 발생한다.

  • 작업을 진행할 때마다 프로세스의 작업 내용이 cpu로 로딩이 된다. 이후 다른 작업을 진행하여야 할 때 기존에 사용하는 것을 내리고 다른 프로세스를 로딩하게 되는데 스레드 방식보다 들어가는 비용이 크다.
  • 여러 프로세스가 하나의 작업을 같이 하는 상황에서 각각의 프로세스들은 다른 프로세스가 가지는 정보를 사용하기 위해 별도의 ICP(Inter Process Communication)가 필요하다.

 

하나의 작업을 병렬로 처리하고 진행하는 작업을 변경하는 과정에서 멀티 쓰레드를 사용한 작업 방식이 더 좋은 효율을 보인다.

 

 

스레드

프로세스 안에서 실제로 작업을 처리하는 단위를 의미하며, 가지고 있는 자원을 이용하여 연산을 처리하는 주체이다.

 

 

멀티 스레드

하나의 프로세스 내에서 둘 이상의 스레드가 여러 작업을 동시에 수행하는 방식을 말한다.

 

CPU 코어는 한 번에 하나의 작업만 수행하므로, 코어의 개수와 동시에 처리되는 최대 작업 개수는 동일하다.

 

 

멀티스레드의 장점

  • 오랜 시간이 걸리는 작업 때문에 Blocking 되지 않고 별도의 작업을 처리할 수 있다.
  • 작업 간의 대기 시간을 효율적으로 활용할 수 있다. 결과가 필요한 작업 외에는 처리한다.
  • 같은 시간 내에 더 많은 작업을 처리할 수 있다. 한도가 정해진 상황
    • 각 스레드의 처리가 동시에 끝나도록 잘 분할해야 하며,
    • 각 쓰레드의 처리가 최대한 다른 스레드를 의존하지 않게끔 해야 한다.

멀티스레드의 단점

  • 멀티 프로세스보단 덜하지만 싱글 스레드보다 사용하는 자원(메모리) 량이 크다.
  • 커널 스레드의 경우 전환할 때 오버헤드(Context Switching)가 발생하기에 총 처리시간이 단일 작업 처리보다 상대적으로 길다. 멀티 프로세스 방식보단 덜하다.
  • 동시성 특유의 문제가 발생한다.
    • 잘못된 데이터의 덮어쓰기
    • 교착 상태의 발생 (각 스레드가 상대방의 리소스가 해제되는 것을 기다리는 상황)
    • 순서가 정해지지 않은 스레드들의 처리 때문에 예외가 발생할 수 있다.
      • 어떠한 자원을 자료구조에 넣어놨을 경우 필요한 스레드가 접근하기 이전에 다른 쓰레드가 가져가게 되고, 그에 따른 예외가 발생한다.
    • 무한 루프가 발생할 수 있다. (Hashmap의 put 연산 등)

그렇기에 구현 시 스레드의 안정성을 고려하여야 한다.

 

 

쓰레드의 안정성?

여러 스레드가 어떤 변수나 함수 또는 클래스 객체에 접근할 때 계속해서 개발자가 의도한 대로 정확하게 동작한다는 것으로 정의한다. 동기화 방식 이외에도 호출하는(사용하는) 쪽에서 동기화 코드 없이도 올바르게 동작할 수 있음을 의미한다.

 

멀티 스레드의 궁극적인 목표는 쓰레드 안정성을 지키면서 성능을 최대한 뽑아내는 것이다.

 

이를 지키기 위해 사용되는 것으로는 불변 객체 구현, Atomic API, 순수 함수 등이 있다.

 

 

스레드의 종류

커널 래벨 (네이티브) 스레드란?

커널을 통해 관리되는 커널 종속적인 스레드이다.

  • 애플리케이션 프로세스의 첫 번째 스레드는 커널 스레드이다.
  • 프로그래머의 요청에 따라 스레드를 생성하고 해당 스레드가 커널을 통해 스케줄링된다면, 커널 레벨 스레드라고 한다.

 

커널 래벨 (네이티브) 스레드의 장점

하나의 프로세서 안에 스레드들을 몇몇 프로세서에 한꺼번에 디스 패치하는 것이 가능함으로 멀티 프로세스 환경에서 매우 빠르게 동작한다.

  • 디스패치? : 준비 상태에 있던 스레드가 CPU에 할당받아 실행되는 것을 말한다.
  • 다른 스레드가 입출력 작업이 끝날 때까지 다른 쓰레드를 사용하여 작업을 진행할 수 있다.
  • 커널이 각 쓰레드를 개별적으로 관리할 수 있다.
  • 쓰레드 작업 시에 Context Switching 이 발생한다.

 

커널 래벨 (네이티브) 스레드의 단점

  • 쓰레드의 관리를 위하여 커널을 호출하는데 일정 시간이 소비된다.
  • 스레드 간의 동기화 문제가 발생할 수 있으며, 그에 따른 처리가 복잡하다.
  • 사용자 스레드에 비하여 자원을 더 많이 소비한다.

 

사용자 (그린) 스레드란?

사용자 영역에서 연산을 수행한다.

  • 프로세스 내부에 존재하는 Main 스레드를 통해 호출되며, Context Switching을 하지 않기에 커널 스레드보다 오버헤드가 적다.
    • Main Thread : Main Method를 실행하는 스레드, JVM이 Application 구동을 위해 사용.
  • 프로세스 내의 하나의 스레드가 커널로 진입하게 된다면, 모든 쓰레드가 Blocking 된다.
  • 사용자 스레드는 커널을 통해 스케줄링이 되지 않기에, 각 CPU에 효율적으로 스레드를 분배할 수 없다.

 

Thread behavior in the JVM

 

Thread behavior in the JVM

The JVM does what it wants to do, so how can you predict the order of thread execution?

www.infoworld.com

 

데몬 스레드란?

(프로세스의) 메인 스레드의 작업을 돕는 보조적인 역활을 하는 쓰레드를 말한다.

  • JVM의 Main 스레드의 작업이 종료된다면, 데몬 쓰레드는 동작을 중지한다.
  • 쓰레드 생성 시에 SetDaemon() 메서드를 이용하여 설정할 수 있다.

 

JVM에서의 데몬 스레드?

  • VM Background Thread : Compile, Optimization, GC를 수행하는 Background 데몬 스레드.
  • 기타 User Thread : thread.SetDaemon()

 

 

Concurrency, Parallelism, Parallel and Concurrency

 

Concurrency?

https://blog.kakaocdn.net/dn/XHuvv/btqUo8YDtkZ/EDBIYFN00kik3nGk1khCAk/img.png

하나의 코어가 여러 프로세스를 번갈아가며 실행하는 것을 의미한다. 이는 사용자에게 동시에 실행되는 것처럼 보이게 만드는 효과를 가지며, 단위 시간 내에 더 많은 일을 처리한다.

 

프로세스 간의 콘텍스트 스위칭이 발생한다.

 

Concurrency의 장단점

장점

  • CPU의 처리량이 증가한다.
  • 자원의 활용도가 증가한다.
  • 프로세스 간의 대기시간이 감소된다.

단점

  • Context Switching에 대한 Overhead가 발생한다.

 

Parallelism?

하나의 프로세스를 분할하여 처리

https://blog.kakaocdn.net/dn/lsJcS/btqUtXB0wCK/whsWh7APILE2QEkbHJpnP0/img.png

여러 개의 코어가 하나의 프로세스의 작업을 분할하여 처리하는 것을 의미할 수 있다. 이는 내부적으로 동작하는 스레드의 개수만큼 CPU에 할당할 수 있음을 의미한다.

 

화면을 랜더링 하는 스레드, 계산을 진행하는 스레드, 서버와 통신하는 스레드 등...

 

한 번에 여러 프로세스를 실행

https://blog.kakaocdn.net/dn/cYwXYo/btqUseRMDBc/AX17UVqB6Xrb59UMMCp1Rk/img.png

이와 같이 각 코어가 별개의 프로세스를 동작시킴으로써 단위 시간 내에 여러 프로세서를 동작시키는 것을 의미할 수도 있다.

 

Parallelism의 장단점

장점

  • 하나의 프로세스를 분할하여 여러 작업으로 처리할 수 있다.
  • 하나의 작업에 대해 가용 자원을 더 많이 할당할 수 있다.
  • 여러 프로세스에 대해서도 동시에 수행할 수 있다.

단점

  • 단일 코어 방식보다 어려운 작성 방식을 가진다.
  • 프로세스 분할 처리 시 발생하는 추가 비용이 더 크다. 데이터 전송, 동기화, 통신, 전환 등
  • 각각의 시스템 아키텍처에 맞게 알고리즘 로직의 조정이 필요하다.

 

Parallel and Concurrency

https://blog.kakaocdn.net/dn/bzCy2i/btqUvCxoJng/dkGcWQfs1JGR9bkIY6CbU1/img.png

여러 개의 코어에서 여러 프로세스들을 번갈아 실행하는 상황을 의미한다.

 

물리적인 개념(Parallel)과 논리적인 개념(Context Switching)이 연계된 것이다.

 

 

Thread 클래스와 Runnable 인터페이스

자바에서 멀티 스레드 방식을 지원하기 위해 존재하는 API이다.

 

 

Java에서 Thread를 생성하는 방법

  1. Runnable 인터페이스를 구현하는 것 필요한 기능만 포함하는 구현체가 필요한 경우
  2. Thread Class를 사용하는 것 일반적인 로직 처리 상에서 Async 한 메서드, Thread Pool 사용 시
  3. Thread Class를 상속받는 것 기존 스레드 기능을 확장해야 하는 경우

 

스레드의 상태

효율적인 스레드 관리를 위해서 알아야할 쓰레드 상태 값

 

NEW : 스레드가 생성되었지만, 아직 시작되지 않은 상태

 

RUNNABLE : 실행 중 또는 실행 가능한 상태

 

BLOCKED : 동기화 블록에 의해서 실행 중지된 상태 lock이 풀릴 때까지 대기

 

WAITING : 스레드의 작업이 대기하고 있는 상태

 

TIMED_WAITING : 특정 시간만큼 대기하도록 지정된 상태

 

TERMINATED : 스레드가 종료된 상태

 

 

스레드의 우선순위

자바 스레드에는 우선순위라는 상태 변수가 존재한다. 이 값에 따라 스레드가 접근 권한을 얻을 때까지의 대기시간이 달라질 수 있는데, 우선순위가 높은 쓰레드가 자주 동작하는 로직을 처리하는 경우 낮은 우선순위를 가지는 스레드는 처리되지 못하고 기아 상태에 빠질 수 있다.

private int priority;

// 우선 순위 상수 값

public static final int MAX_PRIORITY = 10 // 최대 우선 순위

public static final int MIN_PRIORITY = 1 // 최소 우선 순위

public static final int NORM_PRIORITY = 5 //보통 우선 순위

// 우선 순위 관련 메서드

public final int getPriority() {
    return priority;
}

public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) != null) {
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        setPriority0(priority = newPriority);
    }
}

 

 

동기화?

여러 개의 스레드가 하나의 변수, 객체를 사용하고자 할 때, 선점한 스레드를 제외하고 나머지 스레드들은 접근할 수 없도록 대기하게 하는 것이다.

 

자바에서는 synchronized를 사용하여 해당 기능을 지원한다.

  • Java의 synchronized 범위는 Object 단위의 단일 개체 잠금이다.
    • 모든 Object는 하나의 모니터를 지니고 있다.
  • 하나의 스레드가 Monitor의 Lock을 획득하면 내부의 다른 임계 영역에도 접근할 수 있다.
  • 스레드가 객체 내부의 임계영역에 접근할 경우, Lock 을 통해서 접근하게 되며, 다른 쓰레드는 해당 쓰레드가 임계영역에서 나올 때까지 Wait Queue 에서 대기하여야 한다.

 

synchronized Method

  • 해당 Method를 사용하기 위해서는 우선적으로 Lock을 획득하여야 한다.

  • 해당 Method가 Instance Method인 경우 Method를 호출하는 객체에 대하여서 Lock을 획득한다.

    어떠한 스레드가 해당 객체의 method 단위의 임계 영역을 접근한다면, 이 객체의 다른 메서드를 사용하는 스레드들도 모두 Block 상태가 된다.

     

      class b {
    
      A a = new A();
          a.someMethod(); // instance a 에 대한 Lock을 획득하고 호출한다.
    
      }
    
      class A {
          public synchronized void someMethod() {
              // Do SomeThing
        }
    
      }
  • Class (static) Method인 경우 해당 method가 속해진 Class Instance에 대한 Lock을 획득한다.

    즉 해당 class insatance가 여러 개가 존재하더라도, 해당 메서드에 대해 동시 접근이 불가능하다.

    왜냐하면 Class (Static) Object는 Heap 영역에 하나만 존재하기 때문이다.

      class B {
          A.someMethodIsStatic(); // class A 에 대한 Lock을 획득하고 호출한다.
      }
    
      class A {
          public synchronized static void someMethodIsStatic() {
              // Do SomeThing
        }
    
          public synchronized void someMethod() {
              // Do SomeThing
        }
      }

 

 

synchronized Statement

해당 방식은 런타임 내의 분기를 이용하여 동기화가 필요한 작은 공간에서만 Lock을 획득하게끔 합니다.

if (조건식) {
        synchronized (object) {
                // do something
        }
} else {
        // do something
}

 

해당 조건이 맞는 경우에만 동기화를 진행하고 로직 실행

 

Object 내부의 동기화 관련 메서드

 

wait

해당 Object를 선점하고 있다가 접근 권한을 다른 스레드에게 넘기고 notify, notifyAll 이 호출될 때까지 해당 스레드를 대기하게끔 설정하는 메서드이다.

  • Wait Set에 들어가게 된다.
  • 추가 : Block 은 아직 접근조차 하지 못한 상태를 말하는 것이다.

 

notify

해당 Object의 선점을 위해 Wait Set에 대기 중인 하나의 스레드를 실행 대기 상태로 만든다.

  • Entry Set의 스레드들과 같이 경합에 참여한다.

 

notifyAll

해당 Obejct의 선점을 위해 Wait Set에 대기 중인 모든 스레드를 실행 대기 상태로 만든다.

  • notify와 동일한 경합 상태가 발생한다.

 

Object 선점 흐름

 

Object에 대한 Thread 초기 접근.

  • Thread → Entry set → Monitor Lock 획득 시도
  • 선점 Thread Wait() → notify, notifyAll → 대기 중인 Thread 중 Monitor Lock 획득

 

Object에 대한 Thread 접근 (Wait 이후)

  • Wait set → notify, notifyAll → 대기중인 Thread 중 Monitor Lock 획득

Wait Set의 Thread가 임계 영역을 벗어나기 위해서는 접근 권한을 선점하고 Lock을 놓는 방법뿐이다.

 

 

 

synchronized vs java lock?

1.5에 추가된 Concurrent 패키지의 Lock 은 기존의 synchronized 블록을 확장한 좀 더 유연하고 정교한 동기화 방식이다.

 

독점적인 락을 사용해야 할 때, 여러 범위에서 락을 확보하고 사용되는 경우, 세심한 락 조절을 통해 독점적인 락을 사용하는 부분과 시간을 최소화하고, 여러 유틸 성 기능을 제공할 수 있게끔 지원한다.

 

 

synchronized 와의 차이점

  • 별도의 synchronized Statement 등을 만들어 사용하던 방식을 사용하지 않고도 메서드를 통하여서 임계 영역을 지정, 제공할 수 있다. (lock(), unlock())

  • synchronized를 사용할 때 발생했던 문제인 기아상태에 대한 해결책을 지원한다. 공정성 제공 fairness Property

    • synchronized는 스레드 들의 진입 순서를 보장하지 않는다.

      Thread의 기아상태가 발생한다.

  • tryLock()을 통해 해당 임계 영역 접근 불가능할 때 스레드를 차단하지 않고 가능한 경우에만 잠금을 획득하게끔 지원하고, 스레드가 차단된 시간을 감소시킵니다.

  • 임계 영역에 대한 접근 권한을 얻기 위해 대기 중인 스레드는 인터럽트 할 수 없다.

synchronized는 스레드의 진입 순서를 보장하지 않는 반면에 Lock은 진입 순서를 보장한다.

 

 

 

Thread의 기아상태?

다수의 스레드가 동일한 임계영역에 계속해서 접근을 한다면, 하나 이상의 쓰레드가 접근 권한을 받지 못하는 경우가 발생할 수 있다.

 

기아 상태의 원인

  • 높은 우선순위에 따른 스레드들의 CPU 독점

    자바에선 쓰레드들 각각에 대한 우선순위를 설정할 수 있다. (1~10) 일반적으로는 우선순위를 지정하지 않고 사용하는 것이 좋다.

  • 임계 영역 동기화로 인하여 진입 대기 중 Block 상태 (기아 상태)

    스레드가 임계영역으로 진입하기 위해 계속 접근 시도를 하지만, 다른 쓰레드 들의 진입권 획득으로 인하여 영원히 Block된 상태로 남아있을 수 있다는 것이다.

  • wait 호출로 인한 객체에 대한 무한한 대기 상태

    둘 이상의 쓰레드가 동일한 객체의 wait 호출로 인하여 대기 상태에 놓였을 때. notify 메서드는 어떤 스레드를 깨울 것인지 알 수 없다. 깨어나지 못하고 계속 대기할 수 있다.

이 문제를 해결하기 위해선 공정성의 개념을 지원하여야 한다.

 

 

 

Volatile long vs Atomic long

Volatile

해당 변수를 메인 메모리에서만 접근 가능하게끔 하는 것을 말한다.

  • 읽기 연산, 쓰기 연산 등의 결과를 Cache 메모리가 아닌 메인 메모리에서 반영한다.
  • volatile 변수와 같이 사용되는 모든 변수들도 메인 메모리에서 함께 조작된다.
  • Cache 메모리에 변수의 값을 복사해가지 않는다.

스레드 간의 가시성을 해결하기 위한 것으로, 각 스레드는 다른 스레드에서 기록하지 않은 값을 확인할 수 없기에 발생하는 것이 가시성이며, 잘못된 결과로 나타날 수 있다.

 

volatile 만을 사용해볼 만한 상황.

하나의 스레드만이 해당 변수에 대한 연산을 진행하고, 다른 모든 스레드가 조회만 할 경우

  • 읽는 스레드에서 가장 최근에 쓰인 값을 보는 것을 보장한다.

 

volatile의 성능 이슈.

JVM은 프로그램 성능의 향상을 위해 내부 코드에 대하여서 Code reordering를 진행하게 되는데, 동기화되지 않는 코드나 Volatile에 대하여선 진행을 하지 않기에, 상대적으로 성능상의 문제점이 있을 수 있다.

 

사용하지 못하는 상황

여러 스레드가 읽기, 쓰기 연산을 모두 조작하는 경우에는 변경점이 많으므로 문제가 발생할 수 있다.

 

여러 쓰레드 캐싱 값을 쓰기에 메인 메모리에 존재하는 변수 값을 이상하게 덮어쓸 수 있다.

 

이 상황을 해결하기 위해 사용하는 것이 Atomic 패키지이며, Atomic 변수의 개념은 volatile 변수에 대하여서 원자성을 만족하는 연산을 제공하는 것이다. CAS 알고리즘

 

 

 

간단하게 CAS 짚고 넘어가기

CAS에 대한 세 가지 매개변수

  • 현재 값을 교체해야 하는 메모리 위치 V
  • 제일 최근에 스레드를 통해 읽은 값 A
  • V 위치에 덮어써야 되는 새로운 값 B

 

V는 A라는 값을 가지고 있어야 되며, 해당 경우 B를 덮어쓰면 된다. (V == A , V = B)

  • 같은 위치, 연산이라고 판단하게 된다.

그렇지 않은 경우에는 값을 반영하지 않고 재시도한다.

 

 

 

데드락

프로세스, 스레드가 작업에 필요한 모든 자원을 얻지 못하여 선점한 자원을 가지고 대기 중인 상태로 교착 상태라고도 한다.

 

멀티프로그래밍 환경에서 한정된 자원을 여러 곳에서 사용하려 할 때 발생할 수 있다.

 

데드락의 발생 조건

  • 상호 배제

    자원은 한 번에 한 프로세스만이 사용할 수 있어야 한다.

  • 점유 대기

    최소환 하나의 자원을 점유하고 있으면서, 다른 프로세스에 할당되어 사용되고 있는 자원을 사용하기 위해 대기하고 있어야 한다.

  • 비선점

    다른 프로세스, 스레드에 할당된 자원의 접근 권한은 그 작업이 끝나기 전까지 뺏어올 수 없다.

  • 순환 대기

    각각의 프로세스, 스레드가 상대의 자원을 점유하는 상태여야 한다.

'Live Study' 카테고리의 다른 글

Live Study_Week 15. Lambda Expression  (0) 2021.03.01
Live Study_Week 13. I/O  (0) 2021.02.20
Live Study_Week 12. Annotation  (0) 2021.02.02
Live Study_Week 11. Enum  (0) 2021.01.28
Live Study_Week 09. 예외 처리  (0) 2021.01.11

+ Recent posts