Seven Concurrency - Thread & Lock

상호 배제와 메모리 모델

병렬성은 여러 일을 한꺼번에 실행하는 데 관한 것이다.

동시성은 여러 일을 한꺼번에 다루는 데 관한 것이다.

동시성 > 병렬성 : 동시성이 더 큰 개념인가??!!

동시성과 병렬성

  • 멀티쓰레드가 각각 하나의 일을 처리해서 여러 일을 하는 것 : 병렬성
  • 단일쓰레드가 여러 일을 하는 것 : 동시성
  • 멀티쓰레드가 여러 X 여러 일을 하는 것 : 병렬성 + 동시성

사용할 수 있는 것은 synchronized(에이싱크로나이즈드)와 volatile(봘라타일) 뿐

1일차 정리

  • 공유되는 변수에 대한 접근을 반드시 동기화(synchronized)한다.
  • 쓰는 스레드와 읽는 스레드가 모두 동기화되어야 한다.
  • 여러 개의 잠금장치를 미리 정해진 공통의 순서에 따라 요청한다.
  • 잠금장치를 가진 상태에서 외부 메서드를 호출하지 않는다.
  • 잠금장치는 최대한 짧게 보유한다.

내제된 잠금장치를 넘어서

Lock, ReentrantLock

synchronized 비해 신규 추가된 Lock를 이용하면 잠금을 중간에 풀 수도 있고 잠금에 timeout도 지정할 수 있어서 더 낫다.

tryLock(timeout, timeunit)과 같은 방식으로 즉 timeout을 지정해서 데드락를 빠져나가는 방식은 그다지 추천되지는 않는다. 애초에 timeout 대기 시간동안 다른 스레디 들이 대기 상태가 걸리는 것이고, 데드락을 완전히 회피할 순 없다. 또한 라이브락 상황에서 자유롭지 못하다. 다른 완화책들(timeout을 다르게 설정)이 있긴하지만 근본적으로는 데드락을 발생시키지 않게 하는 것 이 더 중요하다.

조건변수

어떤 일을 하기 위해서 조건을 기다리는 것

예를 들면 큐에 담긴 값을 제거하려고 하는 경우 적어도 큐가 하나의 값을 가질 때까지 기다리는 경우, 혹은 버터에 값을 넣을려고 할 때 공간이 생길 때까지 기다리는 경우

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();
try {
    while (!/* 조건이 참이다 */)
        condition.await();

    // 공유되는 자원 사용
} finally {
    lock.unlock();
}

원자변수

AtomicInteger

자체적으로 동시성 이슈를 해결해줌. 말 그대로 메서드가 원자적임 또한 non-blocking(lock-free) 알고리즘 기반이기 때문에 데드락도 없음

volatile

이 키워드를 사용하면 해당 변수를 읽거나 쓰는 동작에 대해 명령어 순서가 뒤바뀌지 않는다. 그저 변수를 쓰거나 읽은 행위의 순서와 원자성만 보장할 뿐이다. 가능하면 volatile를 사용해서 직접 동시성을 제어하기 보다는 java.util.concurrent 를 활용하라

2일차 정리

  • 잠금장치를 얻고자 기다리는 과정을 가로챌 수 있다.
  • 잠금장치를 기다리는 동안 타임아웃이 발생할 수 있다.
  • 잠금장치를 얻고 반납하는 동작이 임의의 순서로 일어날 수 있다.
  • 임의의 조건이 참이 되는 것을 기다리기 위해 조건 변수를 사용할 수 있다.
  • 원자 변수를 이용해서 잠금장치를 사용하는 것을 피할 수 있다.

2일차 자율학습

  • ReenterantLock은 공정성(fairness)을 지원한다. 잠금장치가 “공정하다”는 것은 무엇을 믜미하는 것인가? 공정한 잠금장치를 이용해야하는 이유와 이용하지 않아야하는 이유는 무엇인가?
    • 공정성 옵션이 활성화 되면 잠금시간이 긴 것이 우선 실행된다. 그리고 공정하지 않는 잠금장치를 이용하면 성능이 더 나아진다.
  • ReenterantReadWriteLock은 무엇인가? 그것은 ReentrantLock과 어떻게 다른가? 그것은 언제 사용하면 좋은가?
    • 읽기와 쓰기 별로 락을 분리한 것. 일반적으로 쓰기보다는 읽기가 많은 상황이 많은데 그럴 때 더 유리
  • 불필요한 기상(spurious wakeup)이란 무엇인가? 그런 일은 언제 일어나며, 좋은 코드에서는 그런 일이 일어나지 않아야 하는 이유는 무엇인가?
    • 조건 변수가 만족하지 않았어도 스레드는 깨어나서 조건이 만족되지 않음을 확인하고 다시 잠드는 것을 말하는 것
    • 위와 같은 불필요한 기상이 잦으면 로직이 실행할 수 없음에도 지속적으로 CPU를 소모하게 되므로 성능에 좋지 않음. 그래서 발생하지 않도록 해야함
  • AtomicIntegerFieldUpdater는 무엇인가? 그것은 AtomicInteger와 어떻게 다른가? 그것은 언제 사용하면 좋은가?
    • 리플렉션 기반으로 volatile 을 가지는 변수를 원자화 시키는 유틸리티
    • 필드 직접 접근이 힘들 때 사용하면 좋을 것 같음

거인의 어깨 위에서

굳이 병행성 코드를 만들지 말고 java.util.concurrent 를 사용하자.

CopyOnWrite

항상 방어복사를 하는 것이 아니라 변경(Write)될 때만 복사를 해서 동시성 이슈 제거

완전한 프로그램

자료구조를 이용할 경우 직접 동기화 코드를 만들지 말고 java.util.concurrent 내 스레드에 안전한 자료구조들을 사용하자.

ConcurrentHashMap를 사용할 경우 put 대신 원자적인 putIfAbsent, replace를 잘 이용하자.

3일차 정리

  • 스레드를 직접 만드신 대신 스레드 풀을 이용한다.
  • CopyOnWriteArrayList를 이용해 리스너 관리를 더 쉽고 효과적으로 만든다.
  • 생산자와 소비자가 ArrayBlockingQueue를 이용해 더 효과적으로 의사소통을 한다.
  • ConcurrentHashMap을 이용해 맵에 대한 동시적인 접근을 지원한다.

3일차 자율학습

  • ForkJoinPool에 대한 문서. fork/join 풀은 일반적인 스레드 풀과 어떻게 다른가? 어느 하나를 선호해야하는 이유나 상황은 무엇인가?
    • 작업을 가능한 잘게 쪼개서(재귀적으로) 가능한 병렬처리를 극대화 시키기 위함
  • 작업 훔치기(work stealing)는 무엇이고 어떤 도움을 주는가? java.util.concurrent가 제공하는 도구를 이용해 작업 훔치기를 어떻게 구현하는가?
    • 프로세서의 작업이 종료되면 다른 프로세서의 대기열을보고 작업 항목을 “훔치기(도용)”합니다. 결과적으로, 작업 훔치기는 유휴 프로세서에 대한 스케줄링 작업을 분배하여 프로세서 사용 효율을 극대화 하는 것입니다.
    • ForkJoinPool를 이용하면 구현.
  • CountDownLatchCyclicBarrier는 어떤 차이가 있는가? 어느 하나를 선택해서 사용하는 이유나 상황은 무엇인가?
  • 암달의 법칙(Amdahl’s law)은 무엇인가? 우리의 단어 세기 알고리즘 성능이 개선될 수 있는 이론적인 한계에 대해서 이 법칙은 무엇을 이야기해주는가?

MJ

MJ
Backend 개발자 사람입니다. 어플리케이션의 복잡성을 다루는 DDD에 관심이 많습니다. 어제보다 더 나은 개발자가 되려고 항상 노력합니다.

spring boot 2.4.x 에서 openfeign + hystrix 통합하기

spring-boot 2.4.x spring-cloud 2020.x 의존성 상황에서 feign.hystrix.enabled=true가 안됨`feign.circuitbreaker.enabled=true` 로 바꿔보지만 openfeign과 hystr...… Continue reading

IDDD 14장. 애플리케이션

Published on June 19, 2018