num++;는 다음과 같이 세 단계에 걸쳐 일어난다.
int temp = num;
temp += 1;
num = temp;
따라서 쓰레드가 두개있고 각각의 쓰레드에서 다음과 같은 코드를 수행하면
static int num = 0;
// 쓰레드1
for(int i=0; i<100000; i++)
num++;
// 쓰레드2
for(int i=0; i<100000; i++)
num--;
num의 결과값이 0이 아니게 된다.
왜냐하면 num이 바로 1씩 증가/감소하지 않고 세 단계를 거치기 때문에 그런것이다.
이해를 쉽게 하기 위해 i<100000가 아닌, i<1로 바꿔서 생각해보면
num++; 가 있는 쓰레드가 먼저 num = temp; 라인을 실행한다고 했을때
결과적으로 num의 값은 0이 아닌 -1이 되어버린다.
즉, num++ 3줄이 전부 실행된 후에 num-- 3줄이 실행되어야 0이 출력이 되는데,
2줄 실행되고 중간에 num--가 실행이 되면 문제가 생긴다고 할 수 있다.
더 이상 쪼개질 수 없는 최소 단위
원자성이 적용되지 않는다면 다음과 같은 참사가 발생할 수 있다.
만약 1까지 수행한 후에 서버 크래시가 일어난다면, 10억메소와 함께 집행검이 사라지는 대참사가 발생하게 될 것이다.
Interlocked.Increment/.Decrement(ref num);
// Increments a specified variable and stores the result, as an atomic operation.
// 즉 원자적으로 일을 처리한다는 뜻
그래서 num++이 바로 1증가시키도록 하기 위해 위 함수를 사용할 수 있다.
하지만 성능상 문제가 있다.
lock() {}은 블록단위로 원자성을 보장해준다.
lock()은 내부적으로 Monitor.Enter(); Monitor.Exit();로 구현이 되어 있으니 lock을 쓰도록 하자