일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- leetcode
- HBase
- 파이썬
- DP
- go
- 주키퍼
- 튜토리얼
- 프로그래머스
- 리눅스
- Linux
- 코드워
- golang
- 자바
- programmers
- 동적프로그래밍
- codewars
- zookeeper
- 문제풀이
- Java
- Python
- gradle
- dynamic programming
- redis
- 알고리즘
- OOM
- scala
- 스칼라
- Go언어
- boj
- docker
- Today
- Total
파이문
TimedSemaphore 를 사용하던 멀티 쓰레드가 종료 안되던 문제 본문
ExecutorService 를 사용하여 멀티 쓰레드로 컨슈머를 개발하였다.
컨슈머는 RPS 를 지켜야 하는 룰이 있어서, Apache 의 TimedSemaphore 를 사용하였다.
멀티 쓰레드로 돌리는 작업이 완료가 되면 프로세스가 종료 되게끔 만들었으나 종료 되지 않았다.
shutdown 을 하였는데 왜 종료가 되지 않는 것인지 엄청 삽질을 했다.
사실 ExecutorService 의 shutdown 메서드 자체가 반드시 쓰레드를 종료 시킨다는 보장은 없다.
(ExecutorService shutdown not working 이 구글 서제스트로 있을 정도이다.)
그래서 처음엔 개발을 잘못해서, 쓰레드로 만든 코드에 버그가 있어서 shutdown 으로 종료가 보장이 안되어 shutdownNow 를 호출해야 하는 것인 줄로 파악했다.
그런데 코드가 특별히 어려울 것도 없고, 큐가 비어있으면 제대로 종료가 되었다. (????)
큐에서 아이템 하나만 꺼내와도 컨슈머가 종료가 안되는 버그가 있었던 것이다.
알고보니 TimedSemaphore 가 내부적으로 ScheduleExceutorService를 사용하고 있었다. 그래서 단순히 멀티 쓰레드로 만든 컨슈머만 shutdown 해서는 안되고 컨슈머가 사용하는 Timedsemaphore 도 shutdown 해줬어야 하는 것이다.
큐에서 아이템을 꺼내온 이후에만 컨슈머가 종료되지 않았던 이유는, 내부적으로 아이템이 존재 해야만 semahore 의 acquire 를 호출하게끔 되어 있었는데, TimedSemaphore 의 acquire 또는 tryAquire 를 호출 하면 startTimer 라는 함수를 호출하고 있었다.
/**
* Starts the timer. This method is called when {@link #acquire()} is called
* for the first time. It schedules a task to be executed at fixed rate to
* monitor the time period specified.
*
* @return a future object representing the task scheduled
*/
protected ScheduledFuture<?> startTimer() {
return getExecutorService().scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
endOfPeriod();
}
}, getPeriod(), getPeriod(), getUnit());
}
따라서 TimedSemphore 의 객체를 만들어줬어도 사용하지 않으면 (acquire, tryAqcuire 를 호출하지 않으면) startTimer 가 동작하지 않아서 프로세스가 종료 될 수 있었던 것이다.
참고로 shutdown 을 호출하고 난 이후에는 acquire, tryAcquire 는 호출하지 못한다.
/**
* Prepares an acquire operation. Checks for the current state and starts the internal
* timer if necessary. This method must be called with the lock of this object held.
*/
private void prepareAcquire() {
if (isShutdown()) {
throw new IllegalStateException("TimedSemaphore is shut down!");
}
if (task == null) {
task = startTimer();
}
}
막 사용하지 말고 "API 사용법을 똑바로 읽자" 의 교훈을 다시 한번 깨닫게 되었다.
'TIL' 카테고리의 다른 글
git rm 한 file 복구(reset, restore) 하기 (0) | 2021.02.16 |
---|---|
curl 요청 및 응답 시간 자세히 보기 (0) | 2020.12.10 |
Producer-Consumer 종료하기 (0) | 2020.12.03 |
[Hadoop] TableReducer 로 테이블 여러개 사용하기 (0) | 2020.11.20 |
셸 업그레이드 후 zsh 가 안되는 문제 (0) | 2020.11.06 |