티스토리 뷰

반응형

최근 회사에서 작업을 하면서 문제가 생긴 이력이 있다. 발생한 이슈는 50000개 이상의 데이터를 고객에게 전달해줘야하는 배치 프로그램에서 약 400 ~ 500개의 데이터가 손실된 것이었다. 재빨리 문제를 파악하기 시작하였고 원인은 다음과 같았다.

 

  1. 현재 ArrayList는 heap 영역에 올라가 있어 Thread간에 자원을 공유하고 있는 상황이다.
  2. 자원을 공유하기 때문에 함수를 실행하는 과정에서 race condition이 발생하였고 데이터가 덮어씌워졌다.

이 문제를 해결하기위해 우리는 발생의 근원이 되는 Concurrency(동시성)과 Parallel(병렬성)에 대해 알아보고 문제를 해결하기 위한 전략까지 살펴볼 것이다.

 

Concurrency

동시성은 한마디로 설명하면 동시에 실행되는 것 처럼 보여지는 것이다. Thread나 Process가 context-switching이 되며 작업을 동시에 처리하는 것이다. 

 

Parallel

병렬성은 말 그대로 병렬로 동시에 처리하는 것이다. 보통 multi-core 이상의 환경에서 cpu 자원을 할당 받아 실행되며 작업을 병렬로 처리하는 것이다.

(그림으로 보면 좀 더 이해가 쉬울 것이다.)

 

만약 thread가 위와 같이 동시 또는 병렬로 처리를 한다고 가정했을 때 한 자원을 공유한 상태로 특정한 처리를 한다면 어떤 문제가 발생할 까? 

 

실제로 외부로 데이터를 받아와 Collection에 데이터를 넣어주는 작업을 하고 있다고 생각해보자 그리고 이는 multi-thread 환경이다.

package me.golf.kotlin;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class BoardCount {

    int viewCount = 0;

    public void increaseViewCount() {
        viewCount++;
    }

    public int getViewCount() {
        return viewCount;
    }

    public static void main(String[] args) throws InterruptedException {
        BoardCount boardCount = new BoardCount();
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        CountDownLatch latch = new CountDownLatch(100000);

        for (int i = 0; i < 100000; i++) {
            executorService.submit(
                    () -> {
                        boardCount.increaseViewCount();
                        latch.countDown();
                    }
            );
        }

        latch.await();

        System.out.println(boardCount.getViewCount());

        executorService.shutdownNow();
    }
}

 

멀티 스레드 환경을 구성하여 10개의 Thread pool로 10000번 반복하여 숫자를 증가시키는 프로그램을 짜보았다.

 

결과는 다음과 같았다. 

 

1번 테스트 결과 : 99422

2번 테스트 결과 : 99473

3번 테스트 결과 : 99301

 

보다 싶이 BoardCount는 현재 공유되기 때문에 다음과 같은 문제가 발생하였다. 분명히 100000이 나와야 하지만 여러 스레드가 동시에 순서 없이 마구 읽어 연산하기 때문에 이러한 문제가 생겼다. 

보다 싶이 제대로 읽어오는 경우도 있지만 아닌 경우도 존재한다.

 

멀티 스레드로 동시에 작업을 처리하여 기존 싱글 스레드 보다 훨씬 빨라졌지만 데이터의 일관성이 깨지기 때문에 성능 개선은 전혀 필요없게 된다. 

 

이를 해결하기 위해선 어떻게 해야할까?

 

동기화

멀티 스레드 환경에서 이러한 문제 없이 안전하게 사용하려면 어떻게 해야할까?  

 

동기화를 통해 Thread - safe하게 사용하는것이 제일 좋다. 동기화란 스레드가 순서대로 공유 자원을 점유하여 연산을 하게 끔 유도하는 방법으로 공유 자원 입장에서는 임계 영역 (Critical Section)에서 하나의 스레드만 들어와 작업을 할 수 있게 접근을 제한 하는 것이다.

 

대표적으로 동기화 방법 3가지가 있다. 

 

Mutex와 semaphore 그리고 Monitor가 존재한다. 이후 따로 포스팅을 할 예정이다. 

 

간단하게 설명하자면

 

뮤텍스는 하나의 스레드가 임계 영역에 진입하면 다른 스레드가 들어오지 못하게 Lock을 걸어 접근하지 못하게 막는다.  반면에 세마포어는 1..N 까지의 영역에 스레드가 모두 들어가서 임계영역에 스레드가 더이상 접근하지 못하게 막는다. 

모니터는 좀 더 고차원 적인 동기화 도구로 Java에서는 Monitor를 사용한다. 세마포의 획득P와 반환V의 과정, 추상화 된 P와 V 연산 방식 등은 세마포를 다소 어렵게 만든다.(실수로 V연산을 삽입하지 않으면 데드락 발생) 그렇기 때문에 모니터에서는 자체적으로 처리하기 때문에 개발자의 실수를 방지할 수 있다.

 

자바에서 Thread-safe 하게 사용하는 방법은 이전에 포스팅한 글을 찾아보도록 하자

 

https://golf-dev.tistory.com/23

 

<자바 고급스터디 5주차 - 1부> 자바는 어떻게 Lock을 걸까?

자바는 멀티 스레드 프로그래밍을 지원하는 언어이다. 그렇다 보니 아무 스레드가 와서 마음껏 자원을 읽고 쓰고가 가능하다면 클라이언트가 원하는 정보를 얻기 힘들 것이다. 그렇기 때문에

golf-dev.tistory.com

 

마침.

 

'CS > OS' 카테고리의 다른 글

[Thread chapter.3 Thread 가시성과 원자성]  (0) 2022.11.27
[Thread chapter.01] Thread에 대해서  (0) 2022.11.20
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
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
글 보관함