11. 동시성

GamSa Ham·2023년 1월 25일
0

동시성이 필요한 이유?

동시성은 결합(coupling)을 없애는 전략이다. 즉, 무엇(what)과 언제(when)를 분리하는 전략이다. 스레드가 하나인 프로그램은 무엇과 언제가 서로 밀접하다. 흔히 단일 스레드 프로그램을 디버깅 하는 프로그래머는 정지점(breakpoint)를 정한 후 어느 정지점에 걸렸는지 살펴보면서 시스템을 파악한다.

무엇과 언제를 분리하면 애ㅡㄹ리케이션 구조와 효율이 극적으로 나아진다.

웹 어플리케이션이 표준으로 사용하는 ‘서블릿(Servlet)’모데를 살펴보자. EJB 컨테이너는 동시성을 부분적으로 관리한다.

웹 요청아 들어 올때마다 웹서버는 비동기식으로 서블릿을 실행한다.

또 다른 예로 한번에 한 사용자를 처리하는 시스템이 있다고 가정하자 한 사용자를 처리하는 시간은 1초이다.

사용자가 소수라면 시스템이 아주 빨리 반응 한다. 하지만 사용자 수가 늘어날수록 시스템이 응답하는 속도는 늦어진다.

미신과 오해

  • 동시성은 항상 성능을 높여준다.
    • 동시성은 때로 성능을 높여준다. 대기 시간이 아주 길어 여러 스레드가 프로세서를 공유할 수 있거나, 여러 프로세서가 동시에 처리할 계산이 충분히 많은 경우에만 성능이 높아진다. 어느쪽도 일상적으로 발생하는 상황은 아니다.
  • 동시성을 구현해도 설계는 변하지 않는다.
    • 단일 스레드 시스템과 다중 스레드 시스템은 설계가 판이하게 다르다. 일반적으로 무엇과 언제를 분리하면 시스템 구조가 크게 달라진다.
  • 웹 또는 EJB 컨테이너를 사용하면 동시성을 이해할 필요가 없다.
    • 실제로 컨테이너가 어떻게 동작하는지, 어떻게 동시 수정, 데드락 등과 같은 문제를 피할 수 있는지를 알아야 한다.

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

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

난관

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

두 스레드가 같은 변수를 동시에 참조하면 일부 잘못된 결과를 내놓는다.

동시성 방어 원칙

지금 부터 동시성 코드가 일으키는 문제로부터 시스템을 방어하는 원칙과 기술을 소개한다.

단일책임원칙

동시성은 복잡성 하나만으로도 따로 분리할 이유가 충분하다.

즉 동시성 관련 코드는 다른 코드와 분리해야 한다는 뜻이다.

다음은 동시성을 구현할때 고려하는 항목이다.

  • 동시성 코드는 독자적인 개발, 변경, 조율 주기가 있다.
  • 동시성 코드에는 독자적인 난관이 있다.
  • 잘못 구현한 동시성 코드는 별의별 방식으로 실패한다.

동시성 코드는 다른 코드와 분리하라.

따름 정리: 자료 범위를 제한하라

임계영역(critical section) synchronized 키워드로 보호하라고 권장한다. 이런 임계영역의 수를 줄이는 기술이 중요하다.

공유하는 자료를 수정하는 위치가 많을수록 다음 가능성도 커진다.

  • 보호할 임계영역을 빼먹는다. 그래서 공유 자료를 수정하는 모든 코드를 망가 뜨린다.
  • 모든 임계 영역을 올바로 보호했는지(DRY 위반) 확인하느라 똑같은 노력과 수고를 반복한다.
  • 그러지 않아도 찾아내기 어려운 버그가 더욱 찾기 어려워진다.

권장사항: 자료를 캡슐화(encapsulation)하라. 공유 자료를 최대한 줄여라.

따름 정리: 자료를 사본을 사용하라.

공유 자료를 줄이려면 처음부터 공유하지 않는 방법이 제일 좋다.

객체를 복사해 읽기 전용으로 사용하는 방법이 가능하다.

객체를 복사하는 시간과 부하가 걱정스로울수도 있다. 이럴때는 복사 비용이 진짜 문제인지 시륵해 볼 필요가 있다.

하지만 사본으로 동기화를 피할 수 있다면 내부 잠금 없애 절약한 수행 시간이 사본 생성과 가비지 컬렉션에 드는 부하를 상쇄할 가능성이 크다.

따름 정리: 스레드는 가능한 독립적으로 구현하라.

자신만의 세상에서 존재하는 스레드를 구현하라. 즉, 다른 스레드와 자료를 공유하지 않는다. 각 스레드는 클라이언트 요청 하나를 처리한다.

권장사항: 독자적인 스레드로, 가능하면 다른 프로세서에서, 돌려도 괜찮도록 자료를 독립적인 단위로 분할하라.

라이브러리를 이해하라

자바 5는 동시성 측면에서 이전 버전보다 많이 나아졌다.

  • 스레드 환경에 안전한 컬렉션을 사용한다.
  • 서로 무관한 작업을 수행할 때는 executor 프레임워크를 사용한다.
  • 가능하다면 스레드가 차단(blocking) 되지 않는 방법을 사용한다.
  • 일부 클래스 라이브러리는 스레드에 안전하지 못하다.

스레드 환경에 안전한 컬렉션

스레드에 사용해도 안전한 클래스를 구현했는데 나중에 java.util.concurrent 패키지에 추가되었다.

여기서 제공하는 클래스는 다중 스레드 환경에서 사용해도 안전하며, 성능도 좋다. 동시 읽기/쓰기를 지원하며 자주 사용하는 복합 연산을 다중 스레드 상에서 안전하게 만든 메서드로 제공한다.

좀 더 복잡한 동시성 설계를 지원하고자 자바 5에는 다른 클래스도 추가 되었다.

ReentrantLock, Semaphore, CountDownLatch

권장사항: 언어가 제공하는 클래스를 검토하라

자바에서는 java.util.concurrent, java.util.concirremt.atomic, java.util.concurrent.locks을 익혀라

실행 모델을 이해하라

다중 스레드 애플리케이션을 분류하는 방식은 여러 가지다.

구체적으로 논하기 전에 먼저 몇 가지 기본 용어부터 이해하자.

한전된 자원(Bound Resouce) 데이터 베이스 연겨르 길이가 일정한 읽기/쓰기 버퍼 등이 예다.

상호 배제(Mutual Exclusion) 한 번에 한 스레드만 공유 자료나 공유 자원을 사용할 수 있는 경우를 가리킨다.

기아(Starvation) 한 스레드가 오랫동안 혹은 영원히 자원을 기달린다.

데드락(Deadlock) 여려 스레드가 서로 끝나기를 기달린다.

라이브락(Livelock) 락을 거는 단계에서 각 스레드가 서로를 방해한다.

이제 다중 스레드 프로그래밍에서 사용하는 실제 모델을 살펴 보자

profile
안녕하세요. 자바를 좋아하고 디자인 패턴, Refactoring, Clean Code에 관심이 많은 백엔드 개발자입니다.

0개의 댓글