[클린 코드 읽고 정리해두고 다시 보기] 동시성

inho ha·2024년 9월 22일
0

동시성이 필요한 이유?

  • 동시성은 what과 when을 분리하여 애플리케이션 구조와 효율을 개선하는 전략이다.
  • IO 작업 대기 중에 다른 작업을 수행하여 응답 시간과 작업 처리량 개선이 가능하다.
  • 동시성은 대기 시간이 긴 경우에 성능 향상의 이점이 있다.
  • 그러나 동시성은 성능과 코드 작성 측면에 부하를 유발하고, 복잡하고, 버그 재현이 어렵고, 시스템 구조가 크게 달라질 수 있다.

난관

  • 여러 스레드가 같은 변수를 동시에 참조하면 race condition이 발생할 수 있다.

동시성 방어 원칙

  • SRP를 준수하여 동시성 관련 코드는 다른 코드와 분리해야 한다.
  • 공유 객체를 사용하는 코드 내 critical section을 synchronized 키워드로 보호하라.
  • 자료를 캡슐화하고 공유 자료를 최대한 줄여라.
  • 객체를 복사해 읽기 전용으로 사용하라.
  • 스레드는 가능한 공유 자료 없이 독립적으로 구현하라.

라이브러리를 이해하라

  • 스레드 환경에 안전한 컬렉션을 사용하라.
  • 서로 무관한 작업을 수행할 때는 executor 프레임워크를 사용하라.
  • 가능하다면 스레드가 blocking 되지 않는 방법을 사용한다.
  • 일부 클래스 라이브러리는 스레드에 안전하지 못하다.
  • java.util.concurrent, java.util.concurrent.atomic, java.util.concurrent.locks 이 라이브러리들을 이해하고 활용하여 스레드 환경에 안전한 코드를 구현하라.

실행 모델을 이해하라

  • 생산자 소비자 : 하나 이상 생산자 스레드가 정보를 생성해 버퍼나 큐에 넣는다. 하나 이상의 소비자 스레드가 큐에서 정보를 가져와 사용한다. 큐에서 정보를 가져왔다는 시그널과 큐에 정보를 넣었다는 시그널을 주고 받으며 진행한다. 잘못하면 둘 다 가만히 시그널을 기다릴 수도 있다.
  • 읽기 쓰기 : 읽기 쓰레드가 공유 자원을 읽고, 쓰기 쓰레드가 공유 자원을 갱신한다. 쓰기 스레드가 우선되면 처리율이 떨어지고, 읽기 스레드가 우선되면 기아 현상이 발생할 수 있다. 둘 사이에 균형이 중요하다.
  • 식사하는 철학자들 : 데드락이 가능한 상황이다.

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

  • 공유 객체 하나에는 메서드 하나만 사용하라.
  • 여러 메서드가 필요한 경우, 클라이언트에서 잠그거나, 서버에서 잠그거나, 잠금을 수행하는 중간 단계를 생성하는 것을 고려하라.

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

  • 락은 스레드를 지연시키고 부하를 가중시킨다.
  • critical section의 수를 최대한 줄이고, 그 크기를 작게 만들어라

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

  • 스레드가 blocked 상태에서 종료 시그널을 받지 못하여 데드락이 발생할 수 있다.
  • 종료 코드를 처음부터 고민해서 잘 동작하게 구현하라.
  • 이는 쉽지 않기 대문에 이미 나온 알고리즘을 검토하라.

스레드 코드 테스트하기

  • 문제를 노출하는 테스트 케이스를 작성하고, 프로그램 설정과 시스템 설정과 부하를 바꿔가며 자주 돌리고, 테스트 실패시에 원인을 추적하고, 다시 돌려서 통과하더라도 그냥 넘어가지마라.

말이 안되는 실패는 잠정적인 스레드 문제로 취급하라

  • 대다수 개발자는 스레드가 다른 코드와 교류하는 방식을 직관적으로 이해하지 못한다.
  • 이해하기 어려운 실패에 대해서 일회성 문제로 넘어가지 마라

다중 스레드를 고려하지 않은 순차 코드부터 제대로 돌게 만들자

  • 스레드가 호출하는 POJO를 만들어서 스레드 환경 밖에서 테스트하여 로직 자체가 문제인지 확인하라

다중 스레드를 쓰는 코드 부분을 다양한 환경에 쉽게 끼워 넣을 수 있게 스레드 코드를 구현하라

  • 다양한 설정으로 실행하기 쉽게 구현하여 실행 중 스레드 수를 바꿔보고, 다양한 속도로 실행해보고, 반복 테스트가 가능하도록 테스트 케이스를 작성하라

다중 스레드를 쓰는 코드 부분을 상황에 맞게 조율할 수 있게 작성하라

  • 적절한 스레드 개수를 파악하려면 시행착오가 필요하기 때문에 스레드 개수를 조율하기 쉽게 코드를 구현하라
  • 프로그램 상황에 따라 스스로 스레드 개수를 조정하는 코드도 고민하라

프로세서 수보다 많은 스레드를 돌려보라

  • 시스템이 스레드를 스와핑할 때도 문제가 발생하기 때문에 스와핑이 잦을수록 critical section 누락이나 데드락을 찾기 쉬워진다.

다른 플랫폼에서 돌려보라

  • 운영체제마다 스레드를 처리하는 정책이 달라 결과가 달라질 수 있다.
  • 처음부터 그리고 자주 사용할 가능성이 있는 플랫폼 전부에서 테스트를 수행하라

코드에 보조 코드를 넣어 돌려라. 강제로 실패를 일으키게 해보라

  • 스레드 버그는 코드가 실행되는 수천가지 경로 중에 아주 소수만 실패하기 때문에 산반적이고 우발적이고 재현이 어렵다.
  • wait(), sleep(), yield(), priority() 같은 메서드를 추가해 코드를 다양한 순서로 실행하라
  • 직접 보조 코드를 넣기에는 적정 위치를 찾기 어렵고 배포 환경에 보조 코드가 남아 있으면 성능에 악영향을 미칠 수 있다.
  • 무작위로 sleep이나 yield를 호출하거나 아무 동작도 하지 않는 jiggle 메서드를 구현하여 스레드를 매번 다른 순서로 실행되도록 하면 스레드 오류가 드러날 확률을 높일 수 있다.
profile
inho ha / ian(swatchon) / iha(42seoul)

0개의 댓글