운영체제

Volatile - 메모리가시성

으엉어엉 2024. 12. 15. 13:57
728x90

import static util.MyLogger.log;
import static util.ThreadUtils.sleep;

public class VolatileFlagMain {
    public static void main(String[] args) {
        MyTask task = new MyTask();
        Thread t = new Thread(task,"work");
        log("runFlag = "+task.runFlag);
        t.start();

        sleep(1000);
        log("runFlag = false 로 변경 시도");
        task.runFlag = false;
        log("runFlag = " + task.runFlag);
        log("main 종료");
    }

    static class MyTask implements Runnable {
        boolean runFlag= true;
        //volatile boolean runFlag= true;
        @Override
        public void run() {
            log("task 시작");
            while (runFlag) {

            }
            log("task 종료");
        }
    }
}

runFlag를 false로 했을때 task 끝나고 main 끝나기를 기대했지만 task 즉 work는 쉬지 않고 실행이 무한루프로 돌게 된다. 왜?

메모리 가시성 문제로 생각해볼 수 있다.

 

main 스레드와 work 스레드는 각각의 CPU 코어에 할당되어서 실행된다. 물론 CPU 코어가 1개라면 빠르게 번갈아 가면서 실행될 수 있다.

 

가운데 점선 위는 스레드의 실행 흐름, 아래쪽은 하드웨어를 나타냄

 

CPU 연산은 매우 빠르기 때문에 CPU 연산의 빠른 성능을 따라가려면, CPU 가까이에 매우 빠른 메모리가 필요 한데, 이것이 바로 캐시 메모리이다. 캐시 메모리는 CPU와 가까이 붙어있고, 속도도 매우 빠른 메모리이다. 하지 만 상대적으로 가격이 비싸기 때문에 큰 용량을 구성하기는 어렵다.

runFlag 의 값을 사용하면 CPU는 이 값을 효율적으로 처리하기 위해 먼저 runFlag 를 캐시 메모리에 불러온다. 그리고 이후에는 캐시 메모리에 있는 runFlag 를 사용하게 된다.

주로 컨텍스트 스위칭이 될 때, 캐시 메모리도 함께 갱신되는데, 이 부분도 환경에 따라 달라질 수 있다. 하지만 갱신을 보장하는 것은 아니다.

 

메모리 가시성(memory visibility)

이처럼 멀티스레드 환경에서 한 스레드가 변경한 값이 다른 스레드에서 언제 보이는지에 대한 문제를 메모리 가시성이라 한다. 이름 그대로 메모리에 변경한 값이 보이는가, 보이지 않는가의 문제이다.

 

volatile boolean runFlag= true;

 이것을 사용하면 된다. 여러 스레드에서 사용할때 사용하면 된다. 다만 Cache보다는 느리지만 ...

 

 

 

 

728x90