2017-02-21 4 views
3

우선 읽는 것을 좋아하지 않는 녀석들에 의한 마크 질문을 막기 위해 나는 Producer-Consumer Logging service with Unreliable way to shutdown 질문을 읽었다. 그러나 질문과 대답이 책 본문과 모순되는 것에 대해서는 완전히 대답하지 않습니다. 이제 우리는이 프로세스를 중지하는 방법을 이해하기 위해해야 ​​LogWriter의 경쟁 조건으로 인해 제작자가 차단 될 수 있습니까? [연습 동시성]

public class LogWriter { 
    private final BlockingQueue<String> queue; 
    private final LoggerThread logger; 
    private static final int CAPACITY = 1000; 

    public LogWriter(Writer writer) { 
     this.queue = new LinkedBlockingQueue<String>(CAPACITY); 
     this.logger = new LoggerThread(writer); 
    } 

    public void start() { 
     logger.start(); 
    } 

    public void log(String msg) throws InterruptedException { 
     queue.put(msg); 
    } 

    private class LoggerThread extends Thread { 
     private final PrintWriter writer; 

     public LoggerThread(Writer writer) { 
      this.writer = new PrintWriter(writer, true); // autoflush 
     } 

     public void run() { 
      try { 
       while (true) 
        writer.println(queue.take()); 
      } catch (InterruptedException ignored) { 
      } finally { 
       writer.close(); 
      } 
     } 
    } 
} 

: 책에서

다음 코드를 제공했다. 로깅을 중지해야하지만 이미 커밋 된 메시지를 건너 뛰지 않아야합니다.

저자는 접근 방법 연구 :

public void log(String msg) throws InterruptedException { 
    if(!shutdownRequested) 
      queue.put(msg); 
    else 
      throw new IllegalArgumentException("logger is shut down"); 
} 

을하고 이런 식으로 코멘트 :

LogWriter를 종료하는 또 다른 방법은 플래그가되는 것을 더 메시지를 방지하기 위해 "요청 종료"를 설정하는 것입니다 목록 7.14와 같이 이 제출됩니다. 소비자는 셧다운이 요청되었음을 통보 받으면 대기열을 으로 유출하고, 대기중인 메시지를 모두 털어 내고 을 차단 한 모든 생산자를 차단 해제한다. 그러나이 접근 방식은 경쟁 조건이 있으므로 신뢰할 수 없습니다. 로그 구현은 check-then-act 시퀀스입니다. 프로듀서는 서비스가 아직 종료되지 않았 음을 관찰 할 수 있습니다. 생성자가 로그에 결코 차단되지 않을 수도있는 차단 해제된다. 소비자가 대기열이 고갈되었다고 선언하기 전에 몇 초 기다리는 것과 같은 이럴 가능성을 줄이는 트릭이 있습니다. 그러나 이들은 근본적인 문제를 변경하지 않으며 단지 장애가 발생할 가능성이 높습니다 ( ).

나를 위해 충분한 문구가 있습니다.

나는

if(!shutdownRequested) 
      queue.put(msg); 

원자하지 않고 메시지를 shutdowning 후 큐에 추가 할 수 있음을 이해합니다. 예, 정확하지는 않지만 문제는 없습니다. 큐가 방류 될 때 큐가 비게 될 때 우리는 LoggerThread를 멈출 수 있습니다. 특히 생산자가 차단 될 수있는 이유를 모르겠다.

작성자가 전체 코드를 제공하지 않아서 모든 세부 사항을 이해할 수 없습니다. 나는이 책이 지역 사회 대다수에 의해 읽혔다 고 믿는다. 그리고이 예는 상세한 설명을 가지고있다.

전체 코드 예제로 설명하십시오.

답변

3

이해해야 할 첫 번째 사항은 종료가 요청되면 생산자가 더 이상 요청을 수락하지 않아도되고 소비자 (이 경우 LoggerThread)가 대기열을 비울 필요가 있다는 것입니다. 이 질문에 제시하는 코드는 이야기의 한면만을 보여줍니다. shutdownRequestedtrue 인 경우 추가 요청을 거부합니다.이 예 후, 저자 진행 대답 :

소비자는 다음 보류중인 메시지를 작성, 종료가 요청되었음을 통지 로그에서 차단 어떤 생산자를 차단 해제 될 때 큐를 배출 할 수

맨 먼저 질문에 표시된 queue.takeLoggerThread은 대기열에서 새 메시지를 사용할 수 있도록 무한히 차단합니다. 그러나 LoggerThread (정상적으로 종료)을 종료하려면 LoggerThread의 종료 코드가 shutdownRequested이 무한대가 아닌 queue.take 일 때 실행될 수있는 기회를 확보해야합니다.

저자는 소비자가 그가 의미하는 것은 LogWrittershutdownRequested를 확인할 수 있다는 것입니다 그리고 그것이 사실 인 경우는 큐의 현재 내용을 배출 비 차단 drainTo 메서드를 호출 할 수 있습니다, 큐를 배출 할 수 있다고 할 때 대신 queue.take을 호출하는 대신 (또는 비슷한 비 차단 방법을 호출하십시오.) 대체적으로 shutdownRequested이 거짓 인 경우 LogWriter은 평소대로 queue.take으로 전화 할 수 있습니다.

이 접근법의 실제 문제는 log 메서드 (생성자에서 호출 됨)가 구현되는 방식입니다. 원자가 아니기 때문에 복수 스레드가 shutdownRequested의 설정을 true로 놓칠 수 있습니다. 이 업데이트가 누락 된 스레드 수가 queueCAPACITY보다 큰 경우 어떻게됩니까? log 메서드를 다시 한 번 살펴 보겠습니다. (설명을 위해이 추가 중괄호) 다음 LoggerThread 큐 배수 완료하고 w를 종료하면서 여러 생산자 스레드가 put를 호출하는 단계 E에 나타낸 바와 같이

public void log(String msg) throws InterruptedException { 
    if(!shutdownRequested) {//A. 1001 threads see shutdownRequested as false and pass the if condition. 

      //B. At this point, shutdownRequested is set to true by client code 
      //C. Meanwhile, the LoggerThread which is the consumer sees that shutdownRequested is true and calls 
      //queue.drainTo to drain all existing messages in the queue instead of `queue.take`. 
      //D. Producers insert the new message into the queue.  
      queue.put(msg);//Step E 
    } else 
      throw new IllegalArgumentException("logger is shut down"); 
    } 
} 

, 그것은 가능하다. 1000 번째 스레드가 put을 호출 할 때까지 아무런 문제가 없어야합니다. 진짜 문제는 1001 번째 스레드가 put을 호출 할 때입니다. 대기열 용량이 1000이고, LoggerThread이 더 이상 살아 있거나 queue.take 메소드에 가입하지 않아서 차단됩니다.

+0

같은 두 줄의 코드 사이에서 1000 개의 스레드가 기다리고있을 것으로 예상됩니다. 비록 그것이 이론적으로 가능하다면 여전히 기술적으로 부정확하다는 것을 깨달을지라도. – shmosel

+0

종료 프로세스가 큐 * 및 모든 차단 된 생산자 *를 실제로 소진하는 방법을 보여줄 수 있는지 알고 싶습니다. 테스트에서'drainTo()'는 이미 대기중인 항목 만 배출합니다. – shmosel

+0

@shmosel 동의합니다. 그것은 조금 멀리 가져 왔지만 당신이 내 대답을 오해했다고 생각합니다. 1000 개의 스레드는'put '을 기다리지 않습니다. 그것은 대기중인 1001 번째 스레드입니다. 'put '을 막으려면 큐에 1000 개의 요소가 있어야하고 1001 번째 블록에는 새로운 요소를 넣으려고합니다. 1001 스레드가 큐에 새 요소를 넣으려고 시도 할 경우에만 가능할 수있는 1001 번째 요소에 대한 공간을 소비자가 확보하지 못하도록 보장하면서 ** 모든 소비자가 큐를 비우는 것을 끝내고 이제는 while 루프. 그 단락을 설명 할 다른 방법이 보이지 않습니다! 너? – CKing

관련 문제