[13장] 동시성

DAYEON·2021년 8월 1일
0

Clean Code

목록 보기
14/17
post-thumbnail

객체는 처리의 추상화다. 스레드는 일정의 추상화다.
동시성과 깔끔한 코드는 양립하기 어렵다. 여러 스레드를 동시에 돌리는 이유와 그 어려움, 해결 방법에 대해 알아보자.


동시성이 필요한 이유?

👉 동시성은 무엇과 언제로 분리하는 전략이다. 애플리케이션의 구조와 효율이 극적으로 나아진다.

  • 동시성에 대한 미신
    • 동시성은 항상 성능을 높여준다 (✕)
      → 여러 스레드가 프로세스를 공유할 수 있거나, 동시에 처리할 독립적인 계산이 많은 경우에만!
    • 동시성을 구현해도 설계는 변하지 않는다 (✕)
      → 일반적으로 무엇과 언제를 분리하면 구조가 크게 달라짐!
    • 웹 또는 EJB 컨테이너를 사용하면 동시성을 이해할 필요가 없다 (✕)
      → 동작 방식, 여러 문제 해결 방식에 대한 이해가 필요!

  • 동시성에 대한 타당한 생각
    • 동시성은 부하를 유발한다
    • 동시성은 복잡하다
    • 일반적으로 동시성 버그는 재현하기 어렵다
    • 동시성을 구현하려면 근본적인 설계 전략을 재고해야 한다

난관

👉 동시성을 구현하기 어려운 이유에 대해 알아보자.

  • 경우의 수가 많아지며, 일부 경우의 수가 잘못된 결과를 내놓기 때문

동시성 방어 원칙

👉 동시성 코드의 문제로부터 시스템을 방어하는 원칙과 규칙을 알아보자.

  • 단일 책임 원칙 (SRP)
    • 변경할 이유가 하나여야 한다는 원칙
    • 복잡성이라는 이유 하나 → 동시성 관련 코드는 다른 코드와 분리해야 함
    • 동시성 구현시, 동시성 코드에 대한 고려 사항
      1) 개발/변경/조율 주기가 있다는 점
      2) 독자적인 난관이 존재한다는 점
      3) 동시성 하나만으로도 충분히 어렵다는 점

  • 따름 정리: 자료 범위를 제한하라
    • 공유 객체를 사용하는 코드 내 임계영역을 synchronized 키워드로 보호
    • 자료를 캡슐화하여 공유 자료를 최대한 줄이기

  • 따름 정리: 사본을 사용하라
    • 처음부터 공유하지 않는 방법
      • 객체를 복사해 읽기 전용으로 사용
      • 객체를 복사해 사용한 후 한 스레드가 해당 사본의 결과를 가져옴

  • 따름 정리: 스레드는 가능한 독립적으로 구현하라
    • 자료를 독립적인 단위로 분할 → 다른 스레드와 동기화 할 필요✕
    • 독자적인 스레드로, 가능하면 다른 프로세서에서!

라이브러리를 이해하라

👉 언어, 버전에 따라 스레드 코드 구현시 고려사항을 알아보자.

  • 사용할 라이브러리의 규칙과 주의사항, 고려사항을 이해하자.
  • 언어가 제공하는 주요 클래스를 검토하자.

실행 모델을 이해하라

👉 다중 스레드 애플리케이션의 분류 방식, 기본 용어를 알아보자. 알고리즘과 각 해법 이해하기!

  • 기본 용어
    • 한정된 자원 : 다중 스레드 환경에서 사용하는 크기나 숫자가 제한적인 자원
    • 상호 배제 : 한 번에 한 스레드만 공유 자료/자원을 사용할 수 있는 경우
    • 기아 : 한 스레드나 여러 스레드가 오랫동안 또는 영원히 자원을 기다리는 경우
    • 데드락 : 여러 스레드가 서로가 끝나기를 기다려 진행이 불가한 경우
    • 라이브락 : 락을 거는 단계에서 각 스레드가 서로를 방해해 진행이 불가한 경우

  • 실행 모델
    • 생산자-소비자
    • 읽기-쓰기
    • 식사하는 철학자들

동기화하는 메서드 사이에 존재하는 의존성을 이해하라

👉 공유 클래스 하나에 동기화된 메서드가 여럿이라면 구현이 올바른지 확인하라. 공유 객체 하나에는 메서드 하나만 사용하라.

  • 공유 객체 하나에 여러 메서드가 필요한 상황, 고려할 방법 3가지
    • 클라이언트에서 잠금 : 첫 메서드 호출 전 -🔒- 마지막 메서드 호출
    • 서버에서 잠금 : 서버를 잠금 - 모든 메서드 호출 - 잠금 해제
    • 연결 서버 : 잠금을 수행하는 중간 단계 생성. 원래 서버 변경 ✕

동기화하는 부분을 작게 만들어라

👉 동기화 하는 부분은 최대한 작게!!

  • 임계영역 수를 최대한 줄이자!
  • 필요 이상 임계영역 크기를 키우는 것은 금물!

올바른 종료 코드는 구현하기 어렵다

👉 영구적인 실행 프로그램과 종료되는 프로그램을 구현하는 방법은 다르다. 종료 프로그램이 더 어려움!!

  • 종료 코드를 올바르게 구현하기는 어렵다. 시간을 더 투자할 것!
  • 개발 초기부터 고민하고 구현하기
  • 이미 나온 알고리즘을 검토하기

스레드 코드 테스트하기

👉 문제(시스템 설정, 부하, 플랫폼, 많은 환경)를 노출하는 테스트 케이스 작성해보자. 다시 돌렸더니 통과한다는 이유로 넘어가서는 안된다!

  • 말이 안되는 실패는 잠정적인 스레드 문제로 취급
    • 시스템 실패를 일회성이라 치부하지 않기

  • 다중 스레드를 고려하지 않은 순차 코드부터 제대로 돌게 만들기
    • 먼저 스레드 환경 밖에서 코드를 올바로 돌리기

  • 다중 스레드 코드 부분을 다른 환경에 쉽게 끼워넣을 수 있도록 구현하기

  • 다중 스레드 코드 부분을 상황에 맞게 조율할 수 있도록 작성하기

  • 프로세서 수 보다 많은 스레드 돌려보기
    • 처음부터, 자주 모든 목표 플랫폼에서 코드 돌리기

  • 코드에 보조 코드를 넣어 돌려 강제로 실패를 일으키게 해보기
    • 직접 구현하거나 자동화하는 두가지 방법이 존재
    • 흔들기 기법을 사용해 오류 찾아내기

결론

  • SRP 준수, POJO를 사용해 분리(스레드를 아는 코드, 모르는 코드)
  • 동시성 오류를 일으키는 잠정적 원인 철저히 이해
  • 사용하는 라이브러리와 기본 알고리즘 이해
  • 보호할 코드 영역을 찾는/잠그는 방법 이해
  • TDD 3대 규칙을 따라 테스트 용이성 지키기

인상 깊었던...

깨끗한 동시성은 책 하나를 할당할 정도로 복잡한 주제다.

스레드 코드를 출시하기 전까지 최대한 오랫동안 돌려봐야 한다.


profile
노력하는 초보 개발자

0개의 댓글