클린코드 13장

jiwon·2022년 4월 10일
0

클린코드

목록 보기
13/17
post-thumbnail

13장 동시성

동시성이 필요한 이유?

  • 구조적 개선
  • 응답시간과 작업 처리량 개선

미신과 오해
다음은 동시성과 관련한 일반적인 미신과 오해이다.

  • 동시성은 항상 성능을 높여준다.
  • 동시성을 구현해도 설계는 변하지 않는다.

반대로 다음은 동시성과 관련된 타당한 생각 몇 가지다.

  • 동시성은 다소 부하를 유발한다.
  • 동시성은 복잡하다.
  • 일반적으로 동시성 버그는 재현하기 어려워, 일회성 문제로 여기고 무시하기 쉽다.
  • 동시성을 구현하려면 흔히 근본적인 설계 전략을 재고해야 한다.

난관

두 스레드가 같은 변수를 동시에 참조하면 이상한 결과가 발생할 수 있다..재수없게 동시에 참조하지 않는다면 올바른 결과를 내놓겠지만, 문제는 잘못된 결과를 내놓는 일부 경우이다.

하지만 동시성 코드가 일으키는 문제로부터 시스템을 방어하는 원칙과 기술들이 있다. (아래에 서술)

동시성 방어 원칙

단일 책임 원칙
동시성은 복잡성 하나만으로도 따로 분리할 이유가 충분하다. 동시성 하나만으로도 충분히 어렵기 때문에 주변의 다른 코드가 발목잡지 않도록 하자. 또한 동시성 코드는 독자적인 개발,변경,조율 주기가 있으므로 분리해야 마땅하다.

따름 정리: 자료 범위를 제한하라
동일 필드를 수정하던 두 스레드가 서로 간섭해 이상한 결과를 내놓는 걸 방지하기 위해... 임계영역(critical section)을 synchronized 키워드로 보호하는 것이 좋다.

따름 정리: 자료 사본을 사용하라
공유 자료를 줄이려면 처음부터 공유하지 않는 방법이 제일 좋다. 객체를 복사해 읽기 전용으로 사용하는 방법 등이 사용된다.

따름 정리: 스레드는 가능한 독립적으로 구현하라
각 스레드는 클라이언트 요청 하나를 처리한다. 모든 정보는 비공유 출처에서 가져오며 로컬 변수에 저장한다. 그러면 스레드는 다른 스레드와 동기화할 필요가 없고, 자신만 생각하며 돌아갈 수 있다.

라이브러리를 이해하라

언어가 제공하는 동기화 관련 함수, 클래스 등을 잘 이해하고 써먹자

실행 모델을 이해하라

생산자-소비자
인벤토리라는 버퍼나 큐 변수가 있을때....소비자 스레드가 인벤에서 아이템을 가져오고, 생산자 스레드가 아이템을 인벤에 넣는다고 치자. 여기서 인벤토리는 크기가 정해져 있다. 생산자는 인벤에 빈 공간이 있을때만 아이템 넣을 수 있고, 소비자는 인벤에 아이템이 있어야만 가져갈 수 있다.

생산자 스레드는 '인벤에 아이템이 있다(=니가 가져갈게 있다.)' 라는 시그널을 보내고, 소비자 스레드는 '인벤에 빈 공간이 있다(=니가 넣을 자리가 있다.)' 시그널을 보낸다.

하지만 잘못하면 생산자 스레드와 소비자 스레드가 둘 다 진행 가능함에도 서로에게서 시그널을 기다리고만 있을 가능성이 존재한다.

읽기-쓰기
읽기만 하는 쓰레드와 쓰기만 하는 스레드가 있을때, 대개는 쓰기 스레드가 버퍼를 오랫동안 점유하는 바람에 여러 읽기 스레드가 버퍼를 기다리느라 처리율이 떨어진다. 그렇다고 읽기 스레드만 먼저 처리하면 쓰기 스레드에 기아현상이 발생할 수 있다.

식사하는 철학자들
철학자들이 원탁에 둘러앉아 있다. 철학자는 양손에 포크를 쥐어야만 먹을 수 있다. 만약 모든 철학자가 왼쪽 포크만 잡은 상태라면, 철학자들은 계속 기다리기만 해야 할 것이다..

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

공유 객체 하나에는 메서드 하나만 사용하는 게 좋다.

..하지만 공유 객체 하나에 여러 메서드가 필요한 상황도 생긴다. 그럴때는 아래 3가지 방법을 고려한다.

  • 클라이언트에서 잠금: 클라에서 첫번째 메서드 호출 전 서버 잠그기
  • 서버에서 잠금: 서버에다 '서버를 잠그고 모든 메서드를 호출한 다음에야 잠금 해제하는' 메서드 구현 후, 클라에서 호출
  • 연결 서버: 잠금을 수행하는 중간 단계 생성

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

synchronized문을 남발하는 코드는 좋지 않다. critical section 수를 줄인답시고 거대한 critical section 하나를 만드는 프로그래머도 있는데 매우 나쁜 예이다. 동기화하는 부분은 최대한 작게 만들어라.

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

깔끔하게 종료하는 코드는 구현하기 어렵다. 가장 흔히 발생하는 문제는 데드락(스레드들이 절대로 오지 않는 시그널을 기다리며 무한 대기하는 현상)이다. 그러므로 종료하는 부분을 구현할땐 시간을 투자해 꼼꼼하게 구현하자.

스레드 코드 테스트하기

다시 돌렸더니 통과했다 등의 안일한 태도로 넘어가면 안된다. 스레드 코드 테스트는 고려할 게 많다. 아래에 몇 가지 구체적인 지침이 있다.

  • 말이 안 되는 실패는 잠정적인 스레드 문제로 취급하라.
  • 다중 스레드를 고려하지 않은 순차 코드부터 제대로 돌게 만들자.
  • 다중 스레드를 쓰는 코드 부분을 다양한 환경에 쉽게 끼워 넣을 수 있도록 스레드 코드를 구현하라.
  • 다중 스레드를 쓰는 코드 부분을 상황에 맞춰 조정할 수 있게 작성하라.
  • 프로세서 수보다 많은 스레드를 돌려보라.
  • 다른 플랫폼에서 돌려보라.
  • 코드에 강제로 실패를 유발하는 보조 코드를 넣어 돌려봐라.
profile
개발 공부합니다. 파이팅!

0개의 댓글