2017-11-18

[C++] volatile

 volatile은 C++에서 가장 오해받고 있는 키워드 중 하나일 것이다. 오해받는 이유는 크게 2개라고 생각한다. 첫 번째로 C++의 volatile이 Java나 C# 등 다른 언어에서 말하는 volatile과 다르기 때문이고, 두 번째로 C++의 volatile은 CPU와 컴파일러가 어떻게 동작하는지 알지 않으면 이해하기 어려운 비직관적인 기능이기 때문이라고 생각한다.

 Java나 C# 등 다른 언어에서 volatile은 다른 스레드에서 visibility를 보장해주기 위해 사용된다. 따라서 변수의 접근이 리오더링 되는 것을 막고, 언제나 최신 값을 유지하는 것을 보장해준다. 특히나 멀티 스레드 프로그래밍을 한다면 누구나 한 번쯤 읽어본다는 고전 명작 The Art of Multiprocessor Programming에서 그 예제 코드가 Java로 돼 있기 때문에 C++에서도 volatile 키워드가 같은 의미를 가진다고 생각하는 사람이 많다.

 하지만 C++에서 volatile은 멀티 스레드와 아무런 관련이 없다. 이는 과거 C 표준에 스레드와 관련된 내용이 없던 시절에 추가된 이후로 지금까지 스펙이 변경되지 않았기 때문이다. 그래서 C++의 volatile은 싱글 스레드 코드에서 변수의 접근이 최적화되어 사라지는 것을 막을 뿐이다. 예를 들어 int a가 있을 때 a += 1을 반복하는 코드가 3회 반복될 때, 이 중간에 a에 접근하는 코드가 없으면, 이 코드는 a += 3으로 최적화될 수 있다. 만약 중간에 a에 접근하는 코드가 있어도, 매번 a를 메모리에서 접근하지 않고, a를 레지스터에 집어넣어 최적화할 수 있다. 하지만 a의 타입이 volatile int였다면, 이와 같은 최적화는 할 수 없고 a를 메모리에서 읽어 1을 더하여 메모리에 쓰는 코드가 3번 들어가야 한다.
 하지만 리오더링 되는 것을 막지 않는다. 따라서 의존성이 없는 두 변수가 volatile로 선언돼 있을 때 이 두 변수 사이에 읽기/쓰기는 다른 순서로 실행될 수 있다. 아래의 코드가 volatile이 리오더링되는 것을 막지 않기 때문에 C++이 volatile을 사용했을 때 다른 언어와 다르게 동작하는 대표적인 예제다.
 위의 코드에서 thread1_mainthread2_main이 다른 스레드에서 동시에 실행될 때 코드가 쓰여 있는 순서대로 실행된다면 b가 1이고 a가 0인 순간이 올 수 없기 때문에, 절대로 assert에 걸리지 않는다. 하지만 앞에서 말했듯이 C++의 volatile은 리오더링을 막지 않기 때문에 thread1_mainb = 1a = 1보다 먼저 실행될 수 있어서 b는 1이지만 a는 0인 순간이 올 수 있고 assert에 걸릴 수 있다.

댓글 없음:

댓글 쓰기