인프런 - Rookies님의 강의
https://www.inflearn.com/course/%EC%9C%A0%EB%8B%88%ED%8B%B0-mmorpg-%EA%B0%9C%EB%B0%9C-part4
[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 - 인프런 | 강의
네트워크/멀티쓰레드/운영체제 등 핵심 전공 지식을 공부하고 게임 서버를 바닥부터 만들어보면서 MMORPG 기술을 학습하는 강의입니다., MMORPG 개발에 필요한 모든 기술, C# + Unity로 Step By Step! 🕹
www.inflearn.com
쉽게 말해서 락을 획득할 때 까지 락의 상태를 계속해서 확인하는 것
다음 코드를 보면 이제까지의 싱글쓰레드의 사고방식으로는 아무 문제가 없어 보인다. 실행 결과는 0이 나오겠지?
class SpinLock
{
volatile bool _locked = false;
public void Acquire()
{
while (_locked)
{
// 잠금이 풀리기를 기다림
}
// 내꺼
_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);
}
}
이상하다 이상해.
문제는 멀티쓰레드 환경에서의 "원자성" 을 고려하지 않았다.
다음 코드에서 문제가 발생한다.
public void Acquire()
{
while (_locked)
{
// 잠금이 풀리기를 기다림
}
// 찜!
_locked = true;
}
잠금이 풀리기를 기다리는 부분과 내꺼라고 찜하는 부분이 따로 나뉘어져 있다. 이렇게 되면 두개의 Task가 거의 동시에 _lock이 풀리고 찜해버리는(잠궈버리는) 상황이 발생할 수 있다. 그렇기 때문에 원자성을 고려하여 [잠금이 풀리기를 기다리다가 -> 잠금이 풀리면 -> 찜] 동작 한번에 동작할 수 있도록 만들어야 한다.
C# 에서는 Interlocked로 구현할 수 있는데 두가지 종류가 있다.
1. Interlocked.Exchange(ref int location1, int value)
- value값을 location1에 대입하는 기능을 하고, 반환값으로 value를 대입하기 전의 original 값을 반환한다
- 일반적으로 잘 쓰이지 않는다
2. Interlocked.CompareExchange(ref int location1, int value, int comparand)
- CAS : Compare-And-Swap 방식의 함수로 자주 사용한다
- value와 comparand를 비교해서 같으면 value를 location1에 대입한다
- 마찬가지로 반환값으로 value를 대입하기 전의 original 값을 반환한다
간단한 구현은 다음과 같다
public void Acquire()
{
while (true)
{
// Exchange : location1에 value를 대입
// 반환값 : value를 대입하기 전의 original 값
// 1을 대입했을 때, 원래 값이 0이었다면(잠겨있지 않았다면) 찜할 수 있음 => 반복문 빠져나옴
// 1을 대입했을 때, 원래 값이 1이었다면(잠겨있었다면) 찜할 수 없음 => 반복문 못빠져나옴
int original = Interlocked.Exchange(ref _locked, 1);
if (original == 0)
break;
// CAS : Compare-And-Swap
// value(desired)와 comparand(expected)를 비교해서 같으면 value를 location1에 대입
// 반환값 : value를 대입하기 전의 original 값
int expected = 0; // 예상한 값
int desired = 1; // 원하는 값
if(Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
break;
}
}
주의할 점
lock이 곧 사용가능해 질 경우 context switching을 줄여 CPU의 부담을 덜어주지만, 쓰레드의 작업이 길어져 lock이 안풀릴 경우 CPU 시간을 오히려 많이 소모할 가능성이 있다.
'C# > C# 서버' 카테고리의 다른 글
[C#/서버] ReaderWriterLock (0) | 2023.08.22 |
---|---|
[C#/서버] AutoResetEvent / ManualResetEvent (0) | 2023.08.20 |
[C#/서버] 메모리 배리어 (0) | 2023.08.16 |
[C#/서버] 캐시 이론 (0) | 2023.08.16 |
[C#/서버] 쓰레드 풀 (ThreadPool) (0) | 2023.08.16 |