활동성을 최대로 높이기

데드락

식사하는 철학자 문제

원형 테이블에 둘러 앉은 철학자
자기 왼쪽에 있는 젓가락을 집은 다음, 오른쪽 젓가락을 사용할 수 있을 때까지 기다린 후, 오른쪽 젓가락을 집어서 식사를 한다고 하자.
모든 철학자가 자기가 확보한 왼쪽 젓가락을 놓지 않고 오른쪽 젓가락을 기다리다가 식사를 하지 못하게됨

JVM은 데이터베이스 서버와 같이 데드락 상태를 추적하는 기능 ❌ 데드락은 시스템에 부하가 걸리는 최악의 상황에서 모습을 드러낸다.

락 순서에 의한 데드락

두 개의 스레드가 서로 다른 순서로 동일한 락을 확보하려 할 때 발생 같은 순서로 락을 확보하도록 돼 있다면, 종속성 그래프에 사이클 ❌ > 데드락 ❌ 모든 스레드에서 락을 모두 같은 순서로만 사용한다면 락 순서에 의한 데드락 ❌

동적인 락 순서에 의한 데드락

함수의 인자로 들어온 객체를 대상으로 락을 건다면?

fun transferMoney(from: Account, to: Account) {
  synchronized(from) {
    synchronized(to) {
      // 함수 인자를 통해서 락 순서가 동적으로 변경
    }
  }
}

// 다음의 경우, 락 순서 싸이클이 생기고 데드락 발생 가능성
transferMoney(a, b)
transferMoney(b, a)

Systen.identityHashCode 등의 메서드를 활용해 락 순서를 고정할 필요가 있다. 인자로 들어오는 객체, 혹은 동기화 객체 순서를 정렬해서 균일한 락 순서를 가지도록 해야한다.

객체 간의 데드락

class Taxi {
  val dispatcher: Dispatcher  

  @Synchronized
  fun getLocation(): Point

  @Synchronized // 1. Taxi 객체의 락을 확보
  fun setLocation(...) { 
    dispatcher.notifyAvailable() // 2. Dispatcher 객체의 락을 확보
  } 
}

class Dispatcher {
  @Synchronized // 1. Dispatcher 객체의 락을 확보
  fun notifyAvailable() { 
    taxi.getLocation() // 2. Taxi 객체의 락을 확보
  }
}

// 서로 다른 순서로 락을 가져가려는 상황 발생

락을 확보하고 에일리언 매소드를 호출하는지 체크하기

에일리언 메소드 > 정의는 되어있지만 기능이 만들어져 있지 않은 메소드, 내부에서 어떤 동작이 일어날 지 모르는 메소드

오픈 호출

오픈 호출(open call) > 락을 전혀 확보하지 않은 상태에서 메소드를 호출하는 것

오픈 호출을 활용하면, 활동성 분석도 간편

synchronized 블록도 범위를 최대한 줄이기

프로그램 작성할 때 최대한 오픈 호출한다. 데드락 분석이 쉬워진다.

리소스 데드락

필요한 자원을 사용하기 위해 대기하는 과정도 데드락 ⭕️

스레드 부족 데드락, 다른 작업 실행 결과를 사용해야 하는 작업이 있다면, 스레드 소모성 데드락 원인 되기 쉽다.

데드락 방지 및 원인 추적

가능하다면 한 번에 하나의 락만 사용하게 개발 락 사용 순서를 설계 단계부터 고려

데드락 발생 가능성 여부 판단

여러 개의 락 확보하는 부분 파악
락을 지정된 순서에 맞춰 사용하도록 한다
오픈 호출 방법을 사용하면 이같은 분석 작업이 쉬워진다.

락의 시간 제한

Lock 클래스 등 명시적 락은 일정 시간 동안 락 확보 못하면 tryLock에서 오류 발생시킬 수 있다.

에러를 받고 재시도하도록 할 수 도 있겠다.

스레드 덤프를 활용한 데드락 분석

그 밖의 활동성 문제점

소모, 놓친 신호, 라이브락

소모(starvation)

스레드가 작업 진행하는 데 꼭 필요한 자원을 영영 할당받지 못하는 경우

자바에서 소모 상황이 발생하는 대부분의 원인

스레드 우선 순위를 적절치 못하게 올리거나 내리는 부분 스레드 우선 순위는 왠만하면 건드리지 말자. 활동성 문제를 일으킬 수 있고, 플랫폼 종속적이 되곤 한다.

락을 확보한 채 종료되지 않는 코드 실행 시 👉 다른 스레드는 해당 락을 영영 가저갈 수 없어 소모 상황 발생

형편 없는 응답성

응답성이 떨어지는 상황

라이브락(livelock)

실패할 수밖에 없는 기능을 계속해서 재시도하는 경우 e.g ) 메시지 전송에 실패했을 때, 해당 트랜젝션을 롤백하고 큐의 맨 뒤에 쌓아두는 애플리케이션에서 발생

회복 불가능한 오류를 회복 가능하다고 판단해 계속 재시도하는 과정에서 나타나는 문제

각 스레드가 다른 스레드 응답에 따라 각자의 상태를 계속 변경하느라 실제 작업은 전혀 못하는 경우에도 발생

임의의 시간 동안 기다리다가 재시도 하는 방법 등으로 해결

Last updated