멀티스레드와 동시성

Ch09. 생산자 소비자 문제 - 생산자/소비자 대기 공간 분리

webmaster 2024. 8. 24. 11:48
728x90

생산자/소비자 대기 공간 분리
실행 코드
실행 코드 - Main
실행 결과 - 생산자 먼저 실행
실행 결과 - 소비자 먼저 실행

  • lock.newCondition()을 두 번 호출해서 ReentrantLock을 사용하는 스레드 대기 공간을 2개 만들었다.
  • Condition 분리
    • consumerCond : 생산자를 위한 스레드 대기 공간
    • producerCond : 소비자를 위한 스레드 대기 공간
    • 이렇게 하면 생산자 스레드, 소비자 스레드를 정확하게 나누어 관리하고 깨울 수 있다.
  • put(data) - 생산자 스레드가 호출
    • 큐가 가득 찬 경우: producerCond.await()를 호출해서 생산자 스레드를 생산자 전용 스레드 대기 공간에 보관한다.
    • 데이터를 저장한 경우: 생산자가 데이터를 생산하면 큐에 데이터가 추가된다. 따라서 소비자를 깨우는 것이 좋다. consumerCond.signal()를 호출해서 소비자 전용 스레드 대기 공간에 신호를 보낸다. 이렇게 하면 대기중 소비자 스레드가 하나 깨어나서 데이터를 소비할 있다.
  • take() - 소비자 스레드가 호출
    • 큐가 빈 경우: consumerCond.await()를 호출해서 소비자 스레드를 소비자 전용 스레드 대기 공간에 보관한다.
    • 데이터를 소비한 경우: 소비자가 데이터를 소비한 경우 큐에 여유 공간이 생긴다. 따라서 생산자를 깨우는 것이 좋다. producerCond.signal()를 호출해서 생산자 전용 스레드 대기 공간에 신호를 보낸다. 이렇게 하면 대기 중인 생산자 스레드가 하나 깨어나서 데이터를 추가할 수 있다.

여기서 핵심은 생산자는 소비자를 깨우고, 소비자는 생산자를 깨운다는 점이다.

 

실행 결과 분석

실행 결과 분석 - 초기 상태

  • c1 , c2 , c3는 소비자 스레드 전용 대기 공간( consumerCond )에 대기 중이다.
  • p1 , p2 , p3는 생산자 스레드 전용 대기 공간( producerCond )에 대기 중이다.
  • 큐에 데이터가 비어있다.

생산자 실행

실행 결과 분석 - 생산자 실행1

  • p0 스레드는 ReentrantLock의 락을 획득하고 큐에 데이터를 보관한다.
  • 생산자 스레드가 큐에 데이터를 보관했기 때문에, 소비자 스레드가 가져갈 데이터가 추가되었다.
  • 따라서 소비자 대기 공간( consumerCond )에 signal()을 통해 알려준다.

실행 결과 분석 - 생산자 실행2

  • 소비자 스레드 중에 하나가 깨어난다. c1 이 깨어난다고 가정하자.
  • c1 은 락 획득까지 잠시 대기하다가, 이후에 p0 가 반납한 ReentrantLock의 락을 획득한다. 그리고 큐의 데이터를 획득한 다음에 완료된다

소비자 실행

실행 결과 분석 - 소비자 실행1

  • c0 스레드는 ReentrantLock 의 락을 획득하고 큐에 있는 데이터를 획득한다.
  • 큐에 데이터를 획득했기 때문에, 큐에 데이터를 생산할 수 있는 빈 공간이 생겼다. 생산자 대기 공간 ( producerCond )에 signal()을 통해 알려준다.

실행 결과 분석 - 소비자 실행2

  • 생산자 스레드 중에 하나가 깨어난다. p3 가 깨어난다고 가정하자.
  • p3는 이후에 c0 가 반납한 ReentrantLock의 락을 획득하고, 큐의 데이터를 저장한 다음에 완료된다.

Object.notify() vs Condition.signal()

  • Object.notify()
    • 대기 중인 스레드 중 임의의 하나를 선택해서 깨운다. 스레드가 깨어나는 순서는 정의되어 있지 않으며, JVM 구현에 따라 다르다. 보통은 먼저 들어온 스레드가 먼저 수행되지만 구현에 따라 다를 수 있다.
    • synchronized 블록 내에서 모니터 락을 가지고 있는 스레드가 호출해야 한다.
  • Condition.signal()
    • 대기 중인 스레드 중 하나를 깨우며, 일반적으로는 FIFO 순서로 깨운다. 이 부분은 자바 버전과 구현에 따라 달라질 있지만, 보통 Condition의 구현은 Queue 구조를 사용하기 때문에 FIFO 순서로 깨운다.
    • ReentrantLock을 가지고 있는 스레드가 호출해야 한다.

 

728x90