간단하게 말해서 락을 획득할 때까지 락의 상태를 확인하며 계속 뺑뺑이를 도는 것을 말한다.
아래 코드에서 Thread1과 Thread2가 TaskScheduler에 의해 순서가 정해져서 실행이 된다. 만약 Thread1이 먼저 실행이 된다면 lock을 얻을 것이고 while문에서 if문이 true가 되어 빠져나와 num++을 수행하게 될 것이다. 그사이 Thread1과 거의 동시에 Acquire()를 실행한 Thread2도 while문에 들어와 있는데, 원자성을 적용했기 때문에 둘 다 동시에 while문을 빠져나가는 일 없이 Thread2는 Thread1이 lock을 해제(0)할 때까지 while문 내부에서 무한히 돌게 된다.
스핀락은 쓰레드가 락이 잠겨있는 동안 딴짓을 할 수 없기 때문에, 굉장히 짧은 작업이라 Context Switching으로 부하를 줄 필요가 없을때 사용된다.
이렇게 바쁘게 while문을 돌면서 기다리는 것을 'busy waiting'이라고 하는데, 최대한 CPU를 다른 쓰레드를 돌리는데 사용할 수 없도록 양보하지 않는 것을 말한다.
lock이 곧 사용가능해 질 경우 context switching을 줄여 CPU의 부담을 덜어주지만, 쓰레드의 작업이 길어져 lock이 안풀릴 경우 CPU 시간을 오히려 많이 소모할 가능성이 있다.
class SpinLock
{
volatile int _locked = 0; // 컴파일러가 최적화 수행 안함
public void Acquire()
{
while (true)
{
//int original = Interlocked.Exchange(ref _locked, 1);
//if (original == 0)
// break;
int expected = 0;
int desired = 1;
if (Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
break;
}
}
public void Release()
{
_locked = 0;
}
}
class Program
{
static int _num = 0;
static SpinLock _lock = new SpinLock();
static void Thread1()
{
for(int i=0; i<100000; i++)
{
_lock.Acquire();
_num++;
_lock.Release();
}
}
static void Thread2()
{
for(int i=0; i<100000; i++)
{
_lock.Acquire();
_num--;
_lock.Release();
}
}
static void Main(string[] args)
{
Task t1 = new Task(Thread1);
Task t2 = new Task(Thread2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(_num);
}
}