Java

자바 volatile / C volatile 정리

[하마] 이승현 (wowlsh93@gmail.com) 2015. 9. 1. 10:58

Java volatile



- volatile 변수를 읽어 들일 때 CPU 캐시가 아니라 컴퓨터의 메인 메모리로 부터 읽어들입니다.
그리고 volatile 변수를 쓸 때에도(write) CPU 캐시가 아닌 메인 메모리에 기록합니다.

 - non-volatile 변수들은 어느 시점에 Java Virtual Machine(JVM)이 메인 메모리로 부터 데이터를 읽어 CPU 캐시로 읽어 들이거나 혹은 CPU 캐시들에서 메인 메모리로 데이터를 쓰는지(write) 보장해 줄 수 없습니다. 

- 이때 volatile 을 쓰면, 메모리에 있는 최신의 값을 보기때문에 문제의 소지를 없앨가능성을 높힙니다.

- 하지만 모든걸 해결해주진  못합니다.

- 멀티쓰레드에서 하나의 volatile 변수를 접근할때, 하나의 쓰레드가 아직 메모리에 못 썼다면 , 나머지 쓰레드는
  역시 이전값을 가지게 될것이며,  또는 두개의 쓰레드가 동시에 하나의 변수를 가져다가 작업을 하면 마지막에   쓰여진 값이 최종값이 될것입니다. 두 쓰레드간의 하나의 변수에 대한 동기화가 깨진상태가 됩니다.

- race condition 이 발생할 염려가 되는곳이라면 , synchronized으로 보장해주는것이 필요해보입니다.

java.util.concurrent package 의 AtomicLong or AtomicReference 를 사용하세요.  

- (역주: while (상태변수) {  ....  notify()  } 이런 류의 코드의  상태 변수는 반드시 volatile 해주는것이 좋습니다.)

- volatile 만으로 충분한 상황은  하나의 쓰레드는 쓰고/읽기 , 나머지는 읽기만 한다면 락 없어도 가능합니다.

- volatile 키워드는 32 bit 변수에서만 기능을 보장합니다. 64bit 는 실패될수있습니다. 

- volatile 키워드는 성능에는 아주 조금 안좋겠지요. 필요할때만 사용하십시요. 

Java 5의 volatile 키워드는 단순히 변수를 메인 메모리로 부터 읽고 쓰는것 이상을  보장(guarantees) 해
  줍니다. 아래 내용을 읽어보세요 ( 오해의 소지가 많을듯해서  원문을 직접보시는게 나음) 


The Java volatile Happens-Before Guarantee



C/C++  volatile


volatile 키워드는 앞서 살펴본 하드웨어 제어를 포함하여 크게 3가지 경우에 흔히 사용된다.

 

(1) MMIO(Memory-mapped I/O)

(2) 인터럽트 서비스 루틴(Interrupt Service Routine)의 사용

(3) 멀티 쓰레드 환경


세 가지 모두 공통점은 현재 프로그램의 수행 흐름과 상관없이 외부 요인이 변수 값을 변경할 수 있다는 점이다. 인터럽트 서비스 루틴이나 멀티 쓰레드 프로그램의 경우 일반적으로 스택에 할당하는 지역 변수는 공유하지 않으므로, 서로 공유되는 전역 변수의 경우에만 필요에 따라 volatile을 사용하면 된다.


int done = FALSE;

void main()

{

     ...

     while (!done)

     {

         // Wait

     }

     ...

}

 

interrupt void serial_isr(void)

{

     ...

     if (ETX == rxChar)

     {

         done = TRUE;

     }

     ...

} 

serial.c


위 시리얼 통신 예제는 전역 변수로 done을 선언해서 시리얼 통신 종료를 알리는 ETX 문자를 받으면 main 프로그램을 종료시킨다. 문제는 done이 volatile이 아니므로 main 프로그램은 while(!done)을 수행할 때 매번 메모리에서 done을 새로 읽어오지 않는다는 점이다. 따라서 serial_isr() 루틴이 done 플래그를 수정하더라도 main은 이를 모른 채 계속 루프를 돌고 있을 수 있다. done을 volatile로 선언해주면 매번 메모리에서 변수 값을 새로 읽어오므로 이 문제가 해결된다.

인터럽트의 경우와 마찬가지로 멀티 쓰레드 프로그램도 수행 도중에 다른 쓰레드가 전역 변수 값을 임의로 변경할 수 있다. 하지만 컴파일러가 코드를 생성할 때는 다른 쓰레드의 존재 여부를 모르므로 변수 값이 변경되지 않았다면 매번 새롭게 메모리에서 값을 읽어오지 않는다. 따라서 여러 쓰레드가 공유하는 전역 변수라면 volatile로 선언해주거나 명시적으로 락(lock)을 잡아야 한다.

이처럼 레지스터를 재사용하지 않고 반드시 메모리를 참조할 경우 가시성(visibility) 이 보장된다고 말한다. 멀티쓰레드 프로그램이라면 한 쓰레드가 메모리에 쓴 내용이 다른 쓰레드에 보인다는 것을 의미한다.



레퍼런스

http://tutorials.jenkov.com/java-concurrency/volatile.html  <-- 자바 volatile 설명

http://skyul.tistory.com/337   <-- C/C++ volatile 설명