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를 체크하지 않는다