Java Adv 09 - Producer, Consumer

Produner Consumer

생산자 소비자 문제는 멀티스레드 프로그래밍에서 자주 등장하는 동시성 문제 중 하나로, 여러 스레드가 동시에 데이터를 생산하고 소비하는 상황을 다룬다. 멀티스레드의 핵심을 제대로 이해하려면 반드시 생산자 소비자 문제를 이해하고, 올바른 해결 방안도 함께 알아두어야 한다. 생산자 소비자 문제를 제대로 이해하면 멀티스레드를 제대로 이해했다고 있다. 동시성 문제는 근데 너무 어렵다.

  • 버퍼(Buffer): 생산자가 생성한 데이터를 일시적으로 저장하는 공간이다. 이 버퍼는 한정된 크기를 가지며, 생산자와 소비자가 이 버퍼를 통해 데이터를 주고받는다. 앞서 프린터 예제에서 프린터 큐가 버퍼 역할이다.
  • 생산자가 너무 빠를 : 버퍼가 가득 차서 더 이상 데이터를 넣을 수 없을 때까지 생산자가 데이터를 생성한다. 버퍼가 가득 찬 경우 생산자는 버퍼에 빈 공간이 생길 때까지 기다려야 한다.
  • 소비자가 너무 빠를 : 버퍼가 비어서 더 이상 소비할 데이터가 없을 때까지 소비자가 데이터를 처리한다. 버퍼가 비어있을 때 소비자는 버퍼에 새로운 데이터가 들어올 때까지 기다려야 한다.

예시 코드를 통하여 확인하자

synchronized를 사용하여 한 번에 하나의 스레드만 실행할 수 있고, 다른 스레드는 이 메서드의 실행이 끝날 때까지 대기할 수 있게 대상 객체의 락을 걸어 해당 객체에 대해 다른 스레드의 접근을 제한하기 위해서 synchronized를 사용한다. 뜬금없이 복습을 하자면 안전한 임계 영역을 만드는 이유는 멀티스레드 환경에서 데이터의 일관성을 유지하고, 여러 스레드가 동시에 자원에 접근할 때 발생할 수 있는 데이터 충돌이나 경쟁 조건(race condition)을 방지하기 위해서이다.

image image

생산자가 put으로 request를 요청하게 되면 구현체인 BoundedQueueV1에 있는 queue에 put을 요청하게 된다.
그러면 메인메서드에서 하나하나 확인해보자

image

우선 두가지 조건이 있다. 하나는 producer를 먼저 실행하는 시점, 두번째는 consumer를 먼저 실행하는 시점 producer가 먼저 실행되는 시점에서 보자

생산자 시점

image

결론부터 말하자면 3명의 생산자가 차례대로 큐라는 상자에 데이터를 넣는 걸 보여주고, 나중에 큐 안에 무엇이 있는지생산자들이 어떤 상태인지를 확인하는 작업을 하려고 한다. 생산자는 “data0”, “data1”, “data2”라는 데이터를 큐에 넣는다.

image

  • startProducer: 생산자 스레드를 3개 만들어서 실행한다. 이해를 돕기 위해 0.1초의 간격으로 sleep을 주면서 순차적으로 실행한다. 이렇게 하면 producer1, producer2, producer3의 실행 순서를 확인할 수 있다.
  • printAllstate: 모든 스레드의 상태를 출력한다. 처음에는 생산자 스레드들만 만들어졌으므로 해당 스레드들만 출력한다.
  • startConsumer: 소비자 스레드를 3개 만들어서 실행한다. 이해를 돕기 위해 0.1초의 간격으로 sleep을 주면서 순차적으로 실행한다. 이렇게 하면 consumer1, consumer2, consumer3의 실행 순서를 확인할 수 있다.
  • printAllState: 모든 스레드의 상태를 출력한다. 이때는 생산자와 소비자 스레드 모두 출력된다.

순차실행

생산자 먼저인 producerFirst 를 호출하면 producer1producer2producer3consumer1consumer2consumer3 순서로 실행된다. 소비자 먼저인 consumerFirst 를 호출하면consumer1consumer2consumer3producer1producer2producer3 순서로 실행된다. 참고로 여기서는 이해를 돕기 위해 이렇게 순서대로 실행했다. 실제로는 동시에 실행될 것이다.

하지만 이 코드는 문제가 많다.

편의상 Consumer를 c, Producer를 p라고 말하겠다. 생산자 스레드 먼저 실행의 경우 p3 가 보관하는 data3 은 버려지고, c3 는 데이터를 받지 못한다. (null 을 받는다.) 소비자 스레드 먼저 실행의 경우 c1 , c2 , c3 는 데이터를 받지 못한다.(null 을 받는다.) 그리고 p3 가 보관하는 data3 은 버려진다. 예제는 단순하게 설명하기 위해 생산자 스레드 3개, 소비자 스레드 3개를 한 번만 실행했지만, 실제로 이런 생산자 소비 자 구조는 보통 계속해서 실행된다. 레스토랑에 손님은 계속 찾아오고, 음료 공장은 계속해서 음료를 만들어낸다. 쇼핑몰이라면 고객은 계속해서 주문을 한다. 버퍼가 가득 찬 경우: 생산자 입장에서 버퍼에 여유가 생길 때 까지 조금만 기다리면 되는데, 기다리지 못하고, 데 이터를 버리는 것은 아쉽다. 버퍼가 빈 경우: 소비자 입장에서 버퍼에 데이터가 채워질 때 까지 조금만 기다리면 되는데, 기다리지 못하고, null 데이터를 얻는 것은 아쉽다. 문제의 해결 방안은 단순하다. 앞서 설명한 것 처럼 스레드가 기다리면 되는 것, 그럼 스레드가 기다리도록 구현해보자.

Reference

김영한님의 자바 강의

https://stackoverflow.com/questions/50001353/java-one-producer-and-two-consumers



© 2022. by taewoo

Powered by taewoo