티스토리 뷰

 프로그래머들은 종종 volatilestd::atomic 을 혼동한다. 그러나 이 두 개는 서로 전혀 다른 역할을 한다. std::atomic 은 원자성을 보존해주기 위해서 사용된다. 다시 말하자면 mutex 없이도 두 개의 스레드가 동시에 접근해도 data race 문제가 발생하지 않는다. volatile 은 컴파일러가 최적화하며 특정 라인을 수행하지 않는 경우가 없도록 해준다. 코드로 살펴보자.

volatile int vi(0);
++vi;
--vi;

std::cout << vi;

 위 코드의 출력 결과는 당연히 0이다. 값은 초기와 다르지 않다. 우리가 생각하기에도 무의미한 계산을 한 것이다. 컴파일러는 최적화를 하며 두 개의 라인을 무시할 수 있다. 그런데 만약 임베디드나 특정한 상황에서 저 계산식은 무시가 되면 안되는 경우가 발생한다. 그런 경우에 사용하는 것이 volatile 이다. 컴파일러가 무시할 가능성을 없애주는 것이다. 그렇다고해서 data race 문제는 해결되지 않는다. 두 개의 스레드가 동시에 접근은 가능하다는 의미이다. 반대로 std::atomic 에서는 무의미한 계산을 무시할 수 있다.

코드 재배치

 또다른 차이점으로는 코드 재배치라는 컴파일러 최적화가 있다. 컴파일러는 코드 라인의 순서를 임의대로 바꿀 수 있다. 레지스터에 적재하고 내리는데 사용하는 시간을 최적화하기 위해서 말이다. 그런데 std::atomic 에서는 이야기가 달라진다. std::atomic 을 기록하는 라인이 수행될 때는 그 이전에 나타난 라인들이 수행되어서는 안된다. 이것은 std::atomic 의 특성이다. 다시 말하자면 volatile 은 그럴 필요가 없다는 것이다. 코드 재배치로 라인이 변경될 수 있다.

std::atomic

 좀 더 std::atomic 의 특징들을 살펴보자. std::atomic 은 복사와 이동 연산을 지원하지 않는다. 그 이유는 간단하다. 원자성을 보존해야 하는게 주 목적인데 복사를 위해 읽고 기록하는 작업을 원자적으로 계산하는 하드웨어는 지원하지 않기 때문이다. 다행히 복사를 하는 방법이 가능하다.

std::atomic<int> y(x.load());
y.store(x.load());

레지스터를 이용하여 최적화도 가능한데 C++17 부터는 의미가 없기 때문에 넘어가도록 하겠다.

결론

 우리는 std::atomic volatile 의 차이점에 대해서 알아보았다. 이 두 개는 서로 전혀 다른 역할을 한다는 것을 우리는 알 수 있었다. 따라서 두 개는 한꺼번에 사용도 가능하다.

volatile std::atomic<int> val;

 

 

참고 서적

스콧 마이어스, Effective Modern C++
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
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 31
글 보관함