-
MultiThread 적용 에피소드카테고리 없음 2023. 6. 6. 19:03
스파르타 코딩클럽에서 제공해준 강의를 보다 배운적 없는 Thread 에 흥미가 생겼다
너무 기본을 알려주고 말아서 추가로 더 알아봤는데 ExecutorService 인터페이스를 알게됬다 간단히 보자면
newFixedThreadPool() 반복할 횟수를 넣어 쓰레드를 생성하고,
executorService.execute(() -> { ... }); 으로 영역을 지정해준뒤 교착상태 방지를 위해 예상지점에
synchronized(){ } 를 설정해주고 try catch 로 Exception 에 대한 처리해주면 간단하게 사용할 수 있었다.
이제 기존 코드와 다중 스레드 응답속도를 체크해보자
기존 코드 = Origin, 다중 스레드 = Multi 로 칭함
public ListApiResponse<String> statisticalNumberMulti(StatisticalNumberRequest request, User user) { // Service 생략 ExecutorService executorService = Executors.newFixedThreadPool(value); for (int i = 0; i < value; i++) { executorService.execute(() -> { Map<Integer, Integer> localCountMap = new HashMap<>(countMap); for (int j = 0; j < repetition; j++) { Set<Integer> set = new HashSet<>(); while (set.size() < 6) { int num = rd.nextInt(45) + 1; set.add(num); } for (int num : set) { int count = localCountMap.get(num); localCountMap.put(num, count + 1); } } List<Integer> list = new ArrayList<>(localCountMap.keySet()); list.sort((num1, num2) -> localCountMap.get(num2).compareTo(localCountMap.get(num1))); List<Integer> checkLotto = list.subList(0, 6); Collections.sort(checkLotto); String result = checkLotto.stream().map(Object::toString).collect(Collectors.joining(" ")); synchronized (topNumbers) { topNumbers.add(result); } }); } executorService.shutdown(); long endTime = System.currentTimeMillis(); long executionTime = endTime - startTime; System.out.println("멀티스레드 방식: " + executionTime + " milliseconds"); try { executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } SixNumber sixNumber = new SixNumber(user.getId(), LocalDate.now(), topNumbers); sixNumberRepository.save(sixNumber); saveMainLottoListMulti(topNumbers); return ListApiResponse.ok("요청 성공", topNumbers); }
value = 5, repetition 1000, 888888 두개를 체크해볼 예정이다.
전자는 개인 프로잭트 최소 단위이고,
후자는 필자가 로또 번호 찍는데 이용하는 횟수 ( 결과는 처참하다 5만원 넣어서 5000원 2번 당첨... )
repetition 1000 의 경우 두배 이상 느리게 나온다
repetition 888888 의 경우 약 23.5% 개선
여기서 의문이 생겼다 그럼 value 값을 높여 오버헤드 증가시켰을 때 병렬 처리 이점보다 비용이 더 커질까 라는 그래서 요번엔 value = 2000, repetition 1000 으로 체크해보겠다.
음 ..? 많은 쓰레드를 생성했는데도 Multi 가 빠르다.
value = 2000, repetition 888888 도 비슷한 결과를 보인다.
그럼 오버헤드라는건 어디서 어떻게 발생하는걸까 ? 라는 의문이 있었는데 넘어갔었다 하지만 곧 그 문제를 직면하게 된다
다중 스레드로 처리속도 재미를 봐서 처리가 많은 생길만한 다른 코드에도 적용해보기로 했고 요번단 병렬 처리를 지원하는 parrllelStream() 을 이용하여 처리했는데
private void saveMainLottoListMulti(List<String> list) { Lotto lotto = lottoRepository.findByMain() .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 정보")); List<Integer> countList = lotto.getCountList(); long startTime = System.nanoTime(); list.parallelStream().forEach(sentence -> { String[] numbers = sentence.split(" "); for (String numberStr : numbers) { int num = Integer.parseInt(numberStr) - 1; synchronized (countList) { countList.set(num, countList.get(num) + 1); } } }); long endTime = System.nanoTime(); long executionTime = endTime - startTime; System.out.println("parallelStream: " + executionTime + " nanoTime"); }
해당 로직을 Multi 에서 값을 받아 처리하게 되면 처리속도가 앞도적으로 느리게 찍힌다. 약 130배
Origin :
Multi :
조건식을 바꿔도 결과값을 비슷하다. 이러한 현상을 설명할 순 없지만 필자는 일단 이것이 오버헤드 비용이라 생각하고 더 공부하려고 한다. 서로 다른 Thread 를 한 로직안에서 사용할 때 말도 안되는 비용이 든다라고, 한 로직 안에 하나의 쓰레드만 사용하자라고 이만 마치겠다