동시성

임종혁·2024년 1월 22일

하루에 한 챕터씩 이팩티브 자바랑 클린코드를 읽고 쓴다 다짐한지 어연지 2주 벌써 지켜지지 못했다. ...
뭐 변명좀 하자면 주말에 몸이 정말 안좋았다. 후우
우선 이번 챕터는 동시성 이다.

동시성

동시성이란 멀티 스레드를 사용하는 프로그램을 말한다

사실 멀티스레드를 크게 다루어 본적이 없어 이번 챕터는 그냥 가볍게 읽은거 같다.

동시성이 필요한 이유

  • 동시성은 결합을 없애는 전략이다. 무엇과 언제를 분리하는 전략이다.
  • 무엇과 언제를 분리하면 애플리켇이션 구조와 효율이 극적으로 나아진다
  • 구조적인 관점에서 프로그램이 작은 협력 프로그램 여럿으로 보인다
  • 따라서 시스템을 이해하기 쉽고 문제를 분리하기도 쉽다
  • 구조적 개선만을 위해 동시성을 채택하는 것은 아니다
  • 응답 시간과 작업 처리량 개선이라는 요구사항으로 인해 사용하기도 한다.

단일 스레드는 무엇을 언제 실행하는지 예상할 수 있다.
디버깅 하기엔 좋지만 작업 효율은 떨어진다

ex) 카카오톡 알림톡 유저 100명에세 보낼시 (명당 1초라 가정)
100초가 걸린다
허나 스레드가 3일경우 33초가 걸린다

동시성의 미신과 오해

  • 동시성은 항상 성능을 높여 준다 .

    • 대기 시간이 아주 길어 여러 스레드가 프로세서를 공유할 수 있거나
      여러 프로세서가 동시에 처리할 독립적인 계산이 많을 경우에만 성능이 높아짐
  • 동시성을 구현해도 설계는 변하지 않는다

    • 단일 스레드 시스템과 다중 스레드 시스템 설계가 판이하게 다르다
    • 일반적으로 무엇과 언제를 분리하면 시스템 구조가 크게 달라진다
  • 웹 또는 EJB 컨테이너를 사용하면 동시성을 이해할 필요가 없다

    • 실제로 컨테이너가 어떻게 동작하는지 , 어떻게 동시수정, 데드락 등과 같은 문제를 피할 수 있는지 알아야 한다

동시성 타당한 생각

  • 동시성은 다소 부하를 유발한다. 성능 측면에서 부하가 걸리며 코드도 더 짜야한다
  • 동시성은 복잡하다 , 간단한 문제라도 동시성은 복잡하다
  • 일반적으로 동시성 버그는 재현하기 어렵다. 그래서 진짜 결함으로 간주되지 않고 일회성 무제로 여겨 무시하기 쉬받
  • 동시성을 구현하려면 흔히 근본적인 설계 재고해야 한다

동시성 구현하기 어려운 이유

public class Sequence{
	private int seq = 7;
    
    public int getNextSeq(){
    	return ++seq;
    }
}

위 코드를 보면
x,y 두개 스레드가 동시 호출시
1. x가 8, y가 9를 받는다. seq는 10이 된다.
2. x가 9, y가 8을 받는다. seq는 10이 된다.
3. x가 8, y가 8을 받는다. seq는 9가 된다. (?)

1 과 2 와 같은 답을 원했지만 3이 호출될 경우 동시성 보장이 안된 경우 이다.
이처럼 대다수는 올바른 결과를 내지만, 문제는 잘못된 결과를 내놓는 일부가 존재한다는 것 이다.

동시성 방어 원칙

단일 책임 원칙

단일 책임 원칙은 여기서도 나온다
SRP 클래스 메소드를 변경할 이유는 1가지 여야한다. 책임이 1개 여야한다 .

  • 동시성도 하나의 책임이다
  • 동시성 코드는 복잡하고 어렵다
  • 독자적인 개발 변경 조율 주기가 있다.

자료 범위를 제한 하라

  • 공유 객체를 사용하는 코드 내 임계영역 은 synchronized 키워드로 보호하라
    (임계 영역이란 둘 이상 스레드가 공유 자원에 접근할때 오직 한 스레드만 접근을 허용해야하는 경우)
  • 임계영역의 수를 줄이는 기술이 중요
  • 공유 자료 수정 하는 위치가 많을 수록 커지는 문제
    • 보호할 임계영역을 빼먹는다 그래서 모든 공유 자료 수정하는 모든 코드 망친다
    • 임계영역 올바르게 보호했는지 확인하느라 똑같은 노력과 수고를 반복하낟
    • 찾기 어려운 버그가 더 찾기 어렵다

자료를 캡슐화하여 공유 자료 최대한 줄여라

자료 사본을 사용하라

앞서 말한 임계 영역을 사용하지 않을 거면 사본을 사용하는 것도 방법
공유 자료를 복사해 대체할 수 있는 경우 사용
복사 vs 임계 영역 대한 부하가 걱정일시 테스트를 해보자

스레드는 가능한 독립적으로 구현

  • 자신만의 세상이 존재하는 스레드를 구현 (다른 스레드와 자료 공유하지 않는다)
  • 각 스레드는 클라이언트 요청 하나를 처리
  • 모든 정보는 비공유 출처에서 가져오며 로컬 변수에 저장
  • 그러면 각 스레드는 세상에 자신만 있는듯 돌아간다

독자적인 스레드로 가능하면 다른 프로세서에서 돌려도 괜찮도록 자료를 독립적인 단위로 분할

라이브러리를 이해하라

자바에선 이미 동시성 위한 클래스 가 있다 java.util.concurrent, java.util.concurrent.atomic, java.util.concurrent.locks 살펴보자

실행 모델을 이해하라

  • 한정된 자원

    • 다중 스레드 환경에서 사용하는 한정된 자원 ex)데이터 베이스 연결
  • 상호 배제

    • 한번에 한 스레드만 공유 자원 사용 가능
  • 기아

    • 특정 스레드가 굉장히 오랫동안 또는 영원히 자원을 기다리는 경우
  • 데드락

    • 여러 스레드가 서로 끝나기를 기다리는 경우
  • 라이브락

    • 락을 거는 단계에서 각 스레드가 서로 방해하는 경우

    예제

  • 생성자 소비자

    • 하나 이상 생산자 스레드가 정보를 생성해 빈 공간이 있으면 버퍼나 대기열에 넣는다
    • 하나 이상 소비자 스레드가 대기열에서 정보가 있으면 정보를 가져와 사용한다
    • 생산자 - 소비자 스레드가 사용하는 대기열은 한정된 자원
    • 서로에게 시그널을 보내게 되는데 잘못하면 둘다 진행 가능하지만 무한정 대기(데드락 상황 발생)
  • 읽기 쓰기

    • 읽기 스레드를 위해 주된 정보원 공유자원 사용
    • 쓰기 스레드가 공유 자원 갱신 하는 경우
    • 적절히 균형 잡아 처리율 적당히 높이고 기아 방지법 필요
  • 식사하는 철학자들

    • 기업 애플리케이션은 여러 프로세스가 자원을 얻으려 경쟁한다
    • 주의해서 설계하지 않으면 데드락, 라이브락, 처리율 저하, 효율성 저하등의 상황을 겪을 수 있다.

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

  • 동기화 하는 메서드 사이에 의존성이 존재하면 동시성 코드 찾아내기 어려운 버그 생긴다

  • 공유 클래스 하나에 동기화 된 메서드 여럿이라면 구현이 올바른지 다시 한번 확인해야한다

    공유 객체 하나에는 메서드 하나만 사용하기

    공유 객체 하나에 여러 메서드 필요시

  • 클라이언트 에서 잠금 : 클라이언트에서 첫 메서드를 호출하기 전 서버 잠금

  • 서버에서 잠금: 서버를 잠그고 모든 메서드 호출한 후 잠금을 해제하는 메서드 구현

  • 연결서버 : 잠금을 수행하는 중간 단계 생성

    동기화 하는 부분 작게 만들자

  • 락은 스레드 지연시키고 부하 가중시킨다

  • synchronized 를 남발하면 안된다

  • 코들르 짤때 임계 영역수를 최대한 줄여야한다

  • 그렇다고 임계영역을 키우면 스레드간 경쟁이 늘어나 성능이 떨어진다

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

    종료코드를 개발 초기부터 고민하고 동작하게 구현하라

    스레드 코드 테스트 잘하자

  • 말이 안되는 실패는 잠정적인 스레드 문제로 취급한다

    • 일회성 문제로 치부하지 말고 원인과 해결책 찾기
  • 멀티 스레드 개발 전 단일 스레드 환경에서 돌아가게 만들기

  • 다양한 환경에서 돌리기

  • 스레드 수 쉽게 조절할 수 있게 코드를 작성하기

  • 프로세서 수 보다 많은 스레드 돌려보기

  • 다른 플랫폼 돌려보기

  • 코드에 보조 코드 넣어 강제 실패 일으키기


    확실히 웹 (스프링) 을 이용해서 그런가 그동안 스레드에 관한 고민이 없던것 같다
    뭐 이것도 오해였지만 그레서 아직 스레드에 개념이 제대로 잡히지 못하여 이번 내용은 특히 더 읽기 어려웠던것 같다. 스레드에 개념이 조금 잡힌 후 다시 해당 챕터를 읽어봐야 할 것 같다.

0개의 댓글