1. SpinLock : 존버메타
- Lock풀릴때까지 무한정 기다린다.
느낌적으로 짜보자.
class SpinLock
{
volatile bool _locked = false;
public void Acquire()
{
// 잠금이 풀릴때 까지 대기
while (_locked)
{
}
// 획득
_locked = true;
}
public void Release()
{
_locked = false;
}
}
class Program
{
const int MAX = 100000;
static int _num = 0;
static SpinLock _lock = new SpinLock();
static void Thread_1()
{
for (int i = 0; i < MAX; ++i)
{
_lock.Acquire(); // 획득
++_num;
_lock.Release(); // 해제
}
}
static void Thread_2()
{
for (int i = 0; i < MAX; ++i)
{
_lock.Acquire(); // 획득
--_num;
_lock.Release(); // 해제
}
}
static void Main(string[] args)
{
Task t1 = new Task(Thread_1);
Task t2 = new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(_num);
}
}
결과
- 어림없다.
- Acquire 함수가 Atomic이 보장이 안되기때문.
public void Acquire()
{
// 두개 Thread가 동시에 접근할 경우 _locked는 아직 false이므로
while (_locked)
{
}
// 두 Thread 모두 _locked를 획득하게 된다.
_locked = true;
}
해결법 1.
Interlocked.Exchange 사용
- Atomic을 보장하는 함수
- 값을 ref 변수에 Atomic을 보장하여 넣어준다.
- 반환값은 값을 넣기전에 값
class SpinLock
{
volatile int _locked = 0;
public void Acquire()
{
while (true)
{
// Interlocked으로 _locked에 1을 대입한다.
// 반환값이 0이면 이전에 사용하고 있던 Thread가 없는 것이고,
// 반환값이 1이면 이전에 Thread가 사용하고 있고 아직 Release안된 상태.
int original = Interlocked.Exchange(ref _locked, 1);
if (original == 0)
break;
}
}
public void Release()
{
_locked = 0;
}
}
해결법 2.
위 방법도 괜찮지만 뭔가 직관적이지 않음
- 무조건 Lock을 걸어주고, Lock걸기 이전에 Lock이 안걸려있는 경우에 Lock이 걸린다 : 복잡..
- Lock이 걸려있지 않으면 Lock을 걸어준다 : Good
- 간단한 코드로 써보면
if(_locked == 0)
_locked = 1;
하지만 이렇게하면 Atomic하지 않기 때문에 Interlocked에서 제공되는 함수가 있다.
Interlocked.CompareExchange(ref _locked, 넣을값, 비교값)
- _locked와 '비교값'을 비교하여 같으면 '넣을값'을 _locked에 넣어준다.
- Interlocked.Exchange와 똑같이 original값을 반환한다.
int original = Interlocked.CompareExchange(ref _locked, 1, 0);
if (original == 0)
break;
변수를 사용하여 좀 더 명시적으로 바꾸면
int expected = 0; // 예상한 값
int desired = 1; // 예상한 값이 맞으면 넣을 값
if (Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
break;
전체코드
class SpinLock
{
volatile int _locked = 0;
public void Acquire()
{
while (true)
{
int expected = 0; // 예상한 값
int desired = 1; // 예상한 값이 맞으면 넣을 값
// _locked가 0이면 1을 넣어주고, 0(original == expected)을 반환다.
// original이 0이었다는건 이전에 사용하고있지 않은 상태이기 때문에 사용권을 획득한다.
if (Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
break;
}
}
public void Release()
{
// 이미 Acquire 단계에서 Atomic이 보장된 상황이기때문에 별도 Interlocked없이 해제해도 된다.
_locked = 0;
}
}
'프로그래밍 > 네트워크' 카테고리의 다른 글
Spin Lock 개선 (0) | 2021.09.02 |
---|---|
Lock (0) | 2021.08.29 |
Race Condition (0) | 2021.08.29 |
Cache (0) | 2021.08.27 |
Thread의 생성 (0) | 2021.08.27 |