"제 자리에서는 잘 돌아가는데요." 대신에... | 클린코드 13장

/// <summary>
/// Nomad Coder - 클린코드 - 13장. 동시성
/// @author         : CloudD
/// @last update  : 2025. 06. 19.
/// @update
///     - 2025. 06. 19 : 최초 작성.
/// </summary>


Chat GPT 가 생성해준 '개발자와 Clean Code'


오늘은 13장 “동시성” 을 읽었습니다.
읽는 동안 인상 깊었던 내용과 기억하고 싶은 문장들을 정리해 봅니다.


동시성이 필요한 이유

“동시성은 결합을 없애는 전략이다. 즉, 무엇과 언제를 분리하는 전략이다.”
“무엇과 언제를 분리하면 애플리케이션 구조와 효율이 극적으로 나아진다. 구조적인 관점에서 프로그램은 거대한 루프 하나가 아니라 작은 협력 프로그램 여럿으로 보인다.”
“하지만 구조적 개선만을 위해 동시성을 채택하는 것은 아니다. 많은 입력을 동시에 처리하면 시스템 응답 시간을 높일 수 있다.”

동시성에 대한 미신

  1. 동시성은 항상 성능을 높여준다.
    → NO. 동시성은 때로 성능을 높여준다.
  2. 동시성을 구현해도 설계는 변하지 않는다.
    → NO. 단일 스레드 시스템과 다중 스레드 시스템은 설계가 판이하게 다르다.
  3. 웹 또는 EJB 컨테이너를 사용하면 동시성을 이해할 필요가 없다.
    → NO. 실제로는 컨테이너가 어떻게 동작하는지, 어떻게 동시 수정, 데드락 등과 같은 문제를 피할 수 있는지 알아야 한다.

동시성과 관련된 타당한 생각

  1. 동시성은 다소 부하를 유발한다.
  2. 동시성은 복잡하다.
  3. 일반적으로 동시성 버그는 재현하기 어렵다.
  4. 동시성을 구현하려면 흔히 근본적인 설계 전략을 재고해야 한다.

동시성과 관련된 용어

  1. 한정된 자원 (Bound Resource) : 다중 스레드 환경에서 사용하는 자원으로, 크기나 숫자가 제한적인 것이 특징입니다.
    예 : 데이터베이스 연결, 길이가 일정한 읽기/쓰기 버퍼 등.
  2. 기아 (Starvation) : 한 스레드나 여러 스레드가 굉장히 오랫동안 혹은 영원히 자원을 기다리는 상황입니다.
    예 : 짧은 스레드에게 우선 순위를 주는 정책에서, 짧은 스레드가 지속적으로 이어진다면 긴 스레드는 기아 상태에 빠질 수 있음.
  3. 데드락 (Deadlock) : 여러 스레드가 서로가 가진 자원을 기다리며, 아무도 진행하지 못하는 상태입니다.
  4. 라이브락 (Livelock) : 락을 거는 단계에서 각 스레드가 서로를 방해하는 경우를 말합니다.
    스레드는 계속해서 진행하려 하지만 공명(Resonance) 으로 인해, 굉장히 오랫동안 혹은 영원히 진행하지 못합니다.

난관.

동시성을 구현하기 어려운 이유는 무엇일까요?

위 예제 코드를 참고해주세요.
두 스레드가 동시에 같은 변수를 참조하면 결과가 다양하게 나타납니다.
lastIdUsed 를 42로 설정하고 두 스레드가 동시에 getNextId() 를 호출하면 다음과 같은 결과가 발생할 수 있습니다:

  • A 스레드 : 43 | B 스레드 : 44 | lastIdUsed : 44
  • A 스레드 : 44 | B 스레드 : 43 | lastIdUsed : 44
  • A 스레드 : 43 | B 스레드 : 43 | lastIdUsed : 43
두 스레드가 함수를 호출했을 뿐인데도 결과가 다양하게 나타납니다.
이런 코드가 여러 개라면, 이 함수를 호출하는 스레드가 여럿이라면, 예측하기 어려운 다양한 경우가 생길 수 있습니다. 그리고 그 경우의 수 중에는 잘못된 결과도 포함될 수 있습니다.


동시성 방어 원칙.

이 장에서는 다양한 원칙들을 소개하고 있습니다.
하나 하나 중요한 원칙이지만, 여기에 다 적을 수가 없어서 동시성 방어 원칙에 대해서만 정리해 봅니다.

  1. 단일 책임 원칙 (SRP : Single Responsibility Principle)
    동시성은 복잡성 하나만으로도 따로 분리할 이유가 충분하다.
    권장사항 : 동시성 코드와 다른 코드를 분리하라.
  2. 자료 범위를 제한하라.
    공유 객체를 사용하는 코드 내 임계영역(critical section) 을 synchronized 키워드로 보호할 것을 권장한다. 이런 임계영역의 수를 줄이는 기술이 중요하다.
    권장사항 : 자료를 캡슐화 하라. 공유 자료를 최대한 줄여라.
  3. 자료 사본을 사용하라.
    공유 자료를 줄이려면 처음부터 공유하지 않는 방법이 제일 좋다. 객체를 복사해 읽기 전용으로 사용하는 방법이 가능하다.
  4. 스레드는 가능한 독립적으로 구현하라.
    권장사항 : 독자적인 스레드로, 가능하면 다른 프로세서에서, 돌려도 괜찮도록 자료를 독립적인 단위로 분할하라.

이외에도 많은 원칙을 이야기 합니다.
다중 스레드 애플리케이션을 구현하고 계시다면, 꼭 이 장에서 말하는 원칙들을 살펴보시기를 권합니다.


결론

“다중 스레드 코드는 올바로 구현하기 어렵다. 다중 스레드 코드를 작성한다면 각별히 깨끗하게 코드를 짜야 한다. 주의하지 않으면 희귀하고 오묘한 오류에 직면하게 된다.”
“어떻게든 문제는 생긴다. 그러므로 스레드 코드는 많은 플랫폼에서 많은 설정으로 반복해서 계속 테스트 해야 한다.”

여러 원칙 중 ‘스레드 코드 테스트하기’ 라는 부분에 이런 말이 적혀 있습니다.
“다시 돌렸더니 통과하더라는 이유로 그냥 넘어가면 절대로 안된다.”

개발자들이 자주 하는 실수 또는 변명 중 하나가
“제 자리에서는 잘 돌아가는데요...”
인데, 그만큼 다양한 상황에 대한 대비가 부족했다는 뜻일 수 있습니다.

아주 단순한 프로그램에서도 “어떻게든 문제가 생깁니다”.
무엇을 만들든 충분히 테스트하고, 우연히라도 발견한 오류를 절대 가볍게 넘기지 않는 습관이 필요할 것 같습니다.


CloudD

예술하는 프로그래머, 코딩하는 예술가

댓글 쓰기

다음 이전