동시성

hyyyynjn·2021년 5월 30일
2

자바 스터디

목록 보기
15/15
post-thumbnail
  • 동시성 문제가 발생할 수 있는 상황 2개를 예시들어 설명하기

✋동시성 vs 병렬성

✅동시성(Concurrency)

  • 한 cpu에서 동시에 여러 작업을 하는 것처럼 보이게 만드는 것
  • 동시에 실행되는 것 같이 보이는 것
  • 싱글 코어에서 멀티 쓰레드를 동작 시키는 방식
  • 한 번에 많은 것을 처리
  • 논리적인 개념
  • concurrently ⭕ simultaneously ❌
    • concurrently : N 개의 task 의 실행 시간이 타임라인 상에서 겹칠 수 있다
    • simultaneously : 우리가 일반적으로 사용하는 ’동시에’ 라는 단어의 맥락
  • 한 cpu에서 2개의 process가 있을경우,
    이 두 프로세스는 매우 짧은 시간동안 context switching이 일어나면서 번갈아서 실행된다
  • 사람 눈으로 볼 때 👉 동시에 동작하는 것처럼 보인다

✅병렬성(Parallelism)

  • 다중 cpu에서 작업이 병렬적으로 실행된다. 하나의 cpu당 코드가 실행된다
  • 실제로 동시에 여러 작업이 처리되는 것
  • 멀티 코어에서 멀티 쓰레드를 동작시키는 방식
  • 한 번에 많은 일을 처리
  • 물리적인 개념
  • concurrently ⭕ simultaneously ⭕
  • 데이터 병렬성(Data parallelism)작업 병렬성(Task parallelism)으로 구분된다
  • ✍데이터 병렬성(Data parallelism)
    • 전체 데이터를 멀티 코어의 수만큼 서브 데이터들로 쪼갠뒤, 병렬처리하여 작업을 수행하는 것
    • Java8에서 지원하는 병렬 스트림이 데이터 병렬성을 구현한 것이다
  • ✍작업 병렬성(Task parallelism)
    • 서로 다른 작업을 병렬 처리하는 것
    • 웹 서버는 각각의 브라우저에서 요청한 내용을 개별 쓰레드에서 병렬 처리한다

  • 일반적인 성능 👉 동시성 > 병렬성
    (cpu와 하드웨어를 쓰는 자원이 동시성이 병렬성에 비해 적기 때문)
  • 동시성은 병렬성이기 위한 필요조건이지만 충분조건은 아니다
    • 병렬성을 만족하면, 동시성도 만족하게 된다.
    • 동시성을 만족한다고 병렬성을 만족하는 것은 아니다.
  • 병렬성이 가지는 동시성과의 차이점
    👉 각 코어내의 스레드가 실제로 동시에 명령어를 실행할 수 있다

✋동시성 프로그래밍에서 발생할 수 있는 문제

✅CPU-RAM 아키텍쳐

  • CPU가 작업처리시에,
    RAM에 있는 데이터 일부를 고속 저장 장치 CPU Cache Memory로 읽어들인다.
  • CPU가 작업을 마치고 데이터를 RAM에 저장할 때,
    CPU Cache Memory 에서 RAM으로 쓰기 작업을 수행한다
    • 👉 이 말은
      CPU에서 CPU Cache Memory로 쓰기 작업을 수행하여도,
      RAM으로 쓰기작업을 바로 수행할 필요가 없다는 의미

CPU Cache Memory의 병렬성

CPURAM 중간에 위치한 CPU Cache Memory 의 병렬성으로 두 가지 문제가 발생한다
👉 가시성(visibility) 문제 & 동시 접근 문제

✅가시성(visibility) 문제

하나의 스레드에서 공유 자원(변수, 객체)을 수정한 결과가 다른 스레드에게 보이지 않을 수 있다

✍코드

public class StopThread {
    private static boolean stopRequested;
    
    public static void main(String[] args) throws InterruptedException {

        Thread backgroundThread = new Thread(() -> {
            int i = 0;
            while (!stopRequested)
                i++;
        });

        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}
  • 메인 스레드가 1초 sleep 후 stopRequested = true;을 실행하여 backgroundThread 스레드가 반복문을 빠져나올것 처럼 보이지만 아니다.

  • CPU1에서 수행된 스레드 = backgroundThread
    CPU2에서 수행된 스레드 = mainThread 일때,
    • mainThread에서
      • CPU Cache Memory 2RAM에 공유 변수인 stopRequestedtrue로 쓰기 작업을 완료했지만
    • backgroundThread에서
      • CPU Cache Memory 1에서 읽은 여전히 업데이트 되지 않은 stopRequested값을 사용한다.
        👉 mainThread가 수정한 값을 backgroundThread가 언제쯤에나 보게 될지 보증할 수 없다 (가시성 문제)

✅volatile 키워드 (가시성 문제 해결)

  • volatile로 선언된 변수에 대해서는 CPU Cache Memory를 거치지 않고 RAM으로 직접 읽고 쓰는 작업을 수행하게 된다.
    👉 stopRequested 변수를 volatile로 선언하면 private volatile boolean stopRequested;
    변수 값 불일치 문제를 해결 할 수 있다.

volatile 키워드 사용

  • Multi Thread 환경에서
    • 하나의 Thread만 read & write하고 나머지 Thread가 read하는 상황에서 적합하다
    • 여러 Thread가 write하는 상황에서는 적합하지 않는다
      👉 여러 Thread가 write하는 상황에서는 synchronized를 통해 변수 read & write의 원자성(atomic)을 보장해야한다
  • CPU Cache보다 Main Memory가 비용이 더 크기 때문에 변수 값 일치을 보장해야 하는 경우에만 volatile 사용하는 것이 좋다

✅동시 접근 문제

여러 스레드에서 공유 자원에 동시에 접근하여 변경했을 때 문제가 발생할 수 있다

✍코드

public class IncremantThread {
    private static int count;
    public static void main(String[] args) throws InterruptedException {

        Thread backgroundThread = new Thread(() -> {
            for (int i = 0; i < 10000000; ++i) {
                ++count;
            }
        });
        backgroundThread.start();

        for (int i = 0; i < 10000000; ++i) {
            ++count;
        }

        TimeUnit.SECONDS.sleep(5);
        System.out.println(count);

    }
}
  • 5초후에 결과 값 count20000000으로 출력되지 않는다

  • CPU1에서 수행된 스레드 = backgroundThread
    CPU2에서 수행된 스레드 = mainThread 일때,
    count 변수의 값이 2라고 가정해보자.
    • mainThreadbackgroundThread에서 동시에
      • CPU Cache Memorycount 값을 읽어오게 된다.
      • 두 스레드에서 count값을 1 증가시켜 3이란 값을 각각의 CPU Cache Memory에 저장하게 된다.
    • 👉그 후, 두 CPU Cache Memory에 있는 count 값이 RAM에 저장이 된다면
      3이란 값이 연속으로 중복 저장된다 (동시 접근 문제)

✅synchronized 키워드 (동시 접근 문제 해결)

  • lock을 이용하여 스레드가 공유 자원에 접근시 하나의 스레드만 공유 자원에 접근할 수 있도록 한다

✍코드

public class IncremantThread {
    private static int count;
    public static void main(String[] args) throws InterruptedException {

        Thread backgroundThread = new Thread(() -> {
            for (int i = 0; i < 10000000; ++i) {
                increment();
            }
        });
        backgroundThread.start();

        for (int i = 0; i < 10000000; ++i) {
            increment();
        }

        TimeUnit.SECONDS.sleep(5);
        System.out.println(count);

    }
    
    // synchronized 키워드
    private static synchronized void increment() {
        ++count;
    }
}
  • synchrozied는 가시성의 문제도 해결한다.
  • volatile은 동시 접근의 문제를 해결하지 못 한다.

0개의 댓글