본문 바로가기
WEB/Clean Code

[Clean Code] 13장. 동시성

by IT황구 2022. 2. 25.
728x90
반응형

13장. 동시성

동시성은 왜 필요한가?

  • 결합(coupling)을 없애는 전략이다. 싱글스레드는 what, when이 밀접하다. 이것을 분리한다.
  • 구조적 개선뿐만이 아닌 응답 시간(response time), 출력량(throughput)을 위해서도 필요하다.
    • 메일 정보 수집기
    • 한명씩만 처리하는 시스템

동시성의 미신과 오해

  • 동시성은 성능을 높여준다
    • 동시성은 때로 성능을 높여준다. 즉 항상 높여주는것은 아니다. 여러 스레드가 프로세서를 공유할 수 있거나, 여러 프로세서에서 동시에 처리할 독립적 계산이 많은 경우에만 성능이 높아진다. (일반적인 상황은 아님)
  • 동시성을 구현해도 설계는 변하지 않는다(X)
    • 싱글 스레드와, 다중 스레드는 설계가 다르다. what,when을 분리하면 시스템 구조가 크게 달라진다.
  • 웹 또는 EJB 컨테이너를 사용하면 동시성을 이해할 필요가 없다(X)
    • 그렇지 않다. 어떻게 동시 수정, 데드락을 피할 수 있는지, 실제로 컨테이너가 어떻게 동작하는지 알아야한다

Race Condition으로 인한 문제

public class X {
    private int lastIDUsed;

    public int getNextId() {
        return ++lastIdUsed;
    }
}
  • 2개의 쓰레드가 번갈아가며 getNextId를 호출한다면 순서대로 값이 올라갈까?
  • 그렇지 않다. 왜 그럴까?
  • JAVA로 생각하면..
    • 메서드를 실행하는 잠재적인 경로의 수가 굉장히 많다
    • JIT 컴파일러가 바이트 코드를 처리하는 방식, 자바 메모리 모델이 원자로 간주하는 최소 단위를 알아야 한다.
  • C로 생각하면..
    • +1을 하는 과정에서도 LOAD, ADD ,STORE 최소 3가지를 거쳐야 하는데, 이 과정에서 LOAD, ADD , STORE을 하는 사이에 다른 스레드의 명령이 들어갈 수 있음. 따라서 공유 자원을 사용할땐 조심해야함.

동시성 방어원칙

  • 공유자원을 최대한 줄이자
    • 너무 많으면 보호해야할 critical section을 놓치게 된다
  • SRP
    • 주어진 메서드/클래스/컴포넌트를 변경할 이유가 하나라는 원칙.
    • 동시성 관련 코드는 다른 코드와 분리해야 한다. (복잡성 하나만으로 분리할 이유가 충분)
  • 자료 사본 (copy) 사용하기
    • 이 말은 즉 공유자원을 쓰지 않겠다는 말
    • lock을 걸고 하는 시간보다, copy와 gc에 드는 부하가 비슷할 수 있다. 구현의 용이함에는 사본이 더 편하긴 함.
  • 각 스레드가, 클라이언트 요청 하나를 처리하게 독립적으로 구현한다 (현실성 떨어져보임)
    • 모든 정보를 비공유 출처에서 가져오고 로컬변수에 저장. 다른스레드와 동기화 필요 없음.
  • 라이브러리 이해하기
    • thread-safe컬렉션 사용하기.
    • non-blocking 방법 사용하기
    • JAVA 5의 ConcurrentHashMap

기본 용어

  • 한정된 자원(Bound Resource)
  • 상호 배제(Mutual Exclusion)
  • 기아(Starvation)
  • 데드락(Deadlock)
  • 라이브락(Livelock)

실행 모델 이해하기

  • Producer-Consumer(생산자 소비자 문제)
    • 1개 이상의 producer thread는 정보를 생성해서 큐나, 버퍼에 저장한다
    • 1개 이상의 consumer thread는 대기열에서 정보를 가져와서 사용한다.
    • 큐와 버퍼는 사이즈가 유한함.
    • Producer는 큐나 버퍼가 꽉차면 기다려야함.
    • Consumer는 큐나 버퍼가 비어있으면 정보를 줄 때까지 기다려야함.
    • Producer는 정보를 넣고 Consumer에게 정보를 넣었다고 시그널, Consumer는 Producer에게 큐에 빈공간이 있다고 시그널을 보냄.
    • 문제 발생..
      • 큐가 꽉 찬 상황에서, Consumer가 하나를 읽고 다른 스레드에게 자리가 났다고 보냈지만, 그 스레드가 갑자기 죽어버렸으면, 시그널을 못받고 Producer는 신호가 올때까지 영원히 대기할 수 있음..
  • Readers-Writers
    • 읽기 쓰레드를 위한 정보를 shared resource에서 가져온다고 가정.
    • 쓰기 쓰레드는 공유자원을 가끔 갱신함
    • throughput을 강조하면 starvation이 생기거나, 오래된 정보가 쌓인다.
    • 갱신을 허용하면 처리율에 영향을 미친다. Writer가 버퍼 갱신하는 동안 Reader가 버퍼를 읽지 않고, 그 반대로 읽는동안엔 갱신을 하면 안되는데 이 균형을 잡기가 어렵다.
    • 일반적으로 Writer가 버퍼를 오랫동안 점유해서 Reader가 기다리느라 throughput이 떨어진다.
    • *양쪽 균형을 잡는 방법이 필요하다.
  • Dining Philosophers
    • 철학자를 스레드로, 포크를 자원으로 생각해보자
    • 한 쓰레드가 양쪽의 자원을 모두 가져야 하는데, 계속 한곳씩 사용하고 있다면 그 쓰레드는 영원히 기다리게 될 것이다.

동시성 프로그램 잘 만들기

  • 임계영역 수를 최대한 줄이자. 동기화 하는 영역을 줄이는것이 좋다. (락은 스레드를 지연시키고 부하를 증가시킴)
  • 순차 코드부터 잘 돌게 만들자. 한번에 동시성 프로그램을 만들기는 어렵다.
  • cpu의 코어 개수보다 많은 스레드 돌려보기
    • thread swap시에도 문제가 발생하기에, 코어 개수보다 많은 스레드를 돌리면 데드락을 일으키거나, 임계영역 처리를 놓친 부분을 찾기 쉽다.
  • 다른 플랫폼에서 돌려보자.
  • 강제로 실패를 일으키게 해보자
    • wait(), sleep() , yield()같은것들을 넣어보자

결론

  • 다중 스레드 코드는 짜기가 어렵다.
  • SRP를 준수하자. 다중 스레드 관련 코드는 반드시 분리하자.
  • 동시성 오류를 일으키는 잠정적 원인을 철저히 이해해야 한다.
  • lock은 필요한 부분만. 임계영역은 최소로..
728x90
반응형

'WEB > Clean Code' 카테고리의 다른 글

[Clean Code] 12장. 창발성  (0) 2022.02.24
[Clean Code] 11장. 시스템  (0) 2022.02.24
[Clean Code] 10장. 클래스  (0) 2022.02.23
[Clean Code] 9장. 단위테스트  (0) 2022.02.23
[Clean Code] 8장. 경계  (0) 2022.02.22