(C++) volatile

Posted by : at

Category : Cpp


volatile

변수를 선언할 때 앞에 volatile을 붙이면 컴파일러는 해당 변수를 최적화에서 제외하여 항상 메모리에 접근하도록 만듭니다.

int i = 0;

while (i < 10)
    i++;

printf("%d\n", i);    // 10

volatile을 쓰지않으면 위의 코드는 컴파일러에 의해서

int i = 10;    // 반복문을 없애버리고 10을 할당

printf("%d\n", i);    // 10

이렇게 된다.

volatile int i = 0;    // volatile로 선언하여 항상 메모리에 접근하도록 만듦

// 항상 i의 메모리에 접근해야 하므로 컴파일러는 반복문을 없애지 않음
while (i < 10)
    i++;

printf("%d\n", i);    // 10

volatile과 Pipe line

int32 x = 0;
int32 y = 0;
int32 r1 = 0;
int32 r2 = 0;

volatile bool ready;

void Thread_1()
{
    while(!ready)
        ;

    y = 1; // Store y
    r1 = x; // Load x
}

void Thread_2()
{
    while(!ready)
        ;

    x = 1; // Store x
    r2 = y; // Load y
}

int main()
{
    int32 count = 0;

    while(true)
    {
        ready = false;
        count++;

        x = y = r1 = r2 = 0;

        thread t1(Thread_1);
        thread t2(Thread_2);
        ready = true;       // t1, t2가 동시에 동작하게 하기위해서 넣음.
        t1.join();
        t2.join();

        if(r1 == 0 && r2 == 0)
            break;
    }

    cout << count << "번만에 빠져나옴" << endl;

}

해보면 1000~2000번 안에 빠져나오게 된다.
??? 0을 넣지도 않는데 어떻게 빠져나올수가 있을까??

일단 문제는 두 가지다

  • 가시성
    • CPU는 각 Core마다 각자의 캐쉬가 있다.
    • 따라서 Store, Load된 데이터가 실제 RAM의 값인지 캐쉬의 값인지 알 방법이 없다
  • 코드 재배치
    • 컴파일러 or CPU가 코드를 자신의 환경에 맞게 재배치(최적화)하는 경우가 발생한다.
    • 이런 코드 재배치를 하는 이유가 pipe-line과 연관된다. ```cpp void Thread_1() { while(!ready) ;

      y = 1; // Store y r1 = x; // Load x }

    // 이런코드를 컴파일러가

    void Thread_1() { while(!ready) ;

      // 이렇게 배치할수 있다.
      r1 = x; // Load x
      y = 1; // Store y   }   ```
    
  • CPU에선 연산을 다음 단계를 거쳐서 진행한다.
  • Fetch(코드를 가져오고) - Decode(해석후) - Execute(시행) - Write-back(결과를 통보)
  • 이런 파이프라인을 효율적으로 돌리기위해서 컴파일러 혹은 CPU가 파이프라인에 효율적이게 코드재배치를 진행하게 된다.

volatile과 spinlock

SpinLock을 만들어보자
아래와 같이 구현하면 될까?

int32 sum = 0;
mutex m;

class SpinLock
{
public:
    void lock()
    {
        // 만약 다른곳에서 lock를 해뒀다면 여기서 걸리게 된다.
        while(_locked)
        {

        }

        _locked = true;
    }

    void unlock()
    {
        _locked = false;
    }

private:
    bool _locked = false;
};

void Add()
{
    for(int32 i = 0; i < 10'000; i++)
    {
        lock_guard<SpinLock> guard(m);
        sum++;
    }
}

void Sub()
{
    for(int32 i = 0; i < 10'000; i++)
    {
        lock_guard<SpinLock> guard(m);
        sum--;
    }
}

int main()
{
    thread t1(Add);
    thread t2(Sub);

    t1.join();
    t2.join();
}

일단 문제가 여러개다.

// 1. SpinLock 변수에 volatile선언을 해줘야한다.
// volatile : 최적화를 하지말라

int32 a = 0;
a = 1;
a = 2;
a = 3;
a = 4;
// 컴파일러는 최적화를 하며 a = 4를 바로 넣게된다.

private:
    volatile bool _locked = false;
    // 만약 여기서 volatile을 선언하지 않으면 매번 컴파일러가 _locked를 체크하지 않는다

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links