SpinLock 구현 #1
namespace ServerCore
{
class SpinLock
{
volatile bool _locked = false;
public void Acquire()
{
while (_locked)
{
// lock 풀릴때 까지 무한반복, 즉 남의 lock을 기다림
}
// lock 획득
_locked = true;
}
public void Release()
{
_locked = false;
}
}
internal class Program
{
static int _num = 0;
static SpinLock _lock = new SpinLock();
static void Thread_1()
{
for(int i = 0; i < 100000; i++)
{
_lock.Acquire();
_num++;
_lock.Release();
}
}
static void Thread_2()
{
for (int i = 0; i < 100000; 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);
}
}
}
스레드 1,2가 Acquire을 동시에 실행하여 누가 먼저 lock을 걸지 않은 상태에서 while문을 통과한다면 문제가 됨.
둘다 동시에 lock을 거는 상황이 생기기 때문
SpinLock 구현 #2
namespace ServerCore
{
class SpinLock
{
volatile int _locked = 0;
public void Acquire()
{
while (true)
{
// Interlocked.Exchange는 값을 바꾸기 이전 원본 값(original)을 반환
// _locked를 1로 바꿈(잠금) -> _lock의 원본 값이 0이라면(잠김 풀림 상태)
// 잠금이 풀릴 때 까지 대기할 이유가 없으므로 바로 while문을 빠져나옴
// _lokced의 원본 값이 1이라면(잠긴상태) _locked의 원본 값이 0으로 바뀔 떄 까지(_lock 풀림) 기다림
// _locked의 원본 값은 Realease에 의해 변경됨.
int original = Interlocked.Exchange(ref _locked, 1);
if (original == 0) break;
}
}
public void Release()
{
_locked = 0;
}
}
internal class Program
{
static int _num = 0;
static SpinLock _lock = new SpinLock();
static void Thread_1()
{
for(int i = 0; i < 100000; i++)
{
_lock.Acquire();
_num++;
_lock.Release();
}
}
static void Thread_2()
{
for (int i = 0; i < 100000; 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);
}
}
}
lock을 먼저 걸어버림으로써 해결
namespace ServerCore
{
class SpinLock
{
volatile int _locked = 0;
public void Acquire()
{
while (true)
{
int desired = 1; // lock을 걸길 원함
int expected = 0; // lock이 안걸려 있어야 함
// _locked가 만약 expected라면 desired 값을 넣고 원본 값을 반환
// _locked가 만약 expected와 다르다면 값을 넣지않고 원본 값을 반환
// 만약 lock이 안걸려있다면(_locked == expected) lock을 걸고(_lock = desired)
// 만약 lock이 걸려있다면 (_locked != expected) lock이 풀릴 때 까지 대기
// (_lock의 원본 값이 expected와 같을 때 까지 while문을 반복)
// (_lock의 값은 Release에 의해 바뀌니 Release 즉, lock 해제를 대기)
if (Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
break;
}
}
public void Release()
{
_locked = 0;
}
}
internal class Program
{
static int _num = 0;
static SpinLock _lock = new SpinLock();
static void Thread_1()
{
for(int i = 0; i < 100000; i++)
{
_lock.Acquire();
_num++;
_lock.Release();
}
}
static void Thread_2()
{
for (int i = 0; i < 100000; 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);
}
}
}
비교(lock이 걸려있는가?) 와 변경(lock을 걺) 을 동시에 함
'C# > 네트워크 관련' 카테고리의 다른 글
ReaderWriterLock 구현 (2) | 2024.09.22 |
---|---|
ReaderWriterLock 예제 (0) | 2024.09.20 |
Deadlock 예시 코드 (0) | 2024.09.12 |
캐시와 캐시지역성 (0) | 2024.08.26 |
Task 예시 (0) | 2024.08.23 |