이 글은 Inflearn - Rookiss : 게임서버 강의를 듣고 정리한 글입니다.
목차.
개요
이번 글에서 Lock을 이용할 때 멀티 스레드로 인해 원하는 결과가 나오지 않는 상황과 해결하는 방법을 다뤄보겠습니다.
using System;
using System.ComponentModel;
using System.Numerics;
namespace ServerCore
{
class SpinLock
{
volatile bool _locked = false;
public void Acquire()
{
while (_locked)
{
}
_locked = true;
}
public void Release()
{
_locked = false;
}
}
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);
}
}
}
위 코드는 Acquire() 함수가 호출되면 계속해서 while 반복문이 돌다가 Release() 함수를 통해 _locked에 false가 할당되면 반복문이 멈추게 되고 다음 코드가 실행되는 방식입니다.
확실히 이 코드라면 루프를 도는 동안 다른 코드가 접근하지 못하기 때문에 0이라는 결과가 나와야 합니다.
실행시키면 어떤 결과가 나올까요?
0이 나올 것이라는 예상과는 달리 무작위 값이 출력됩니다.
이러한 결과의 원인은 다음과 같습니다.
멀티 스레드로 인해 하나의 스레드만 Acquire에 접근한 게 아니라 가끔 동시다발적으로 접근해서 발생한 문제이다.
그럼 어떻게 문제를 해결할 수 있을까?
Interlocked.CompareExchage 활용
using System;
using System.ComponentModel;
using System.Numerics;
namespace ServerCore
{
class SpinLock
{
volatile int _locked = 0;
public void Acquire()
{
while (true)
{
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 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);
}
}
}
이 코드의 핵심은 CompareExchange() 함수를 사용한다는 것이다.
CompareExchage 함수는 스레드 간 값을 변경하거나 교환하기 위해 사용되는 함수입니다.
이 함수를 사용하면 여러 스레드가 동시에 값을 수정하려고 할 때 발생할 수 있는 문제를 방지하고, 그 결과 값을 안전하게 수정할 수 있습니다.
Interlocked.CompareExchange()의 매개변수로
location : 값을 변경하려는 변수의 참조를 나타냅니다.
value : 변수에 새로 설정할 값을 나타냅니다.
comparand : 변수의 현재 값과 비교할 값을 나타냅니다.
정리
위에서 사용한 코드를 해석해 보자면
비교할 값은 0이고 만약 일치한다고 하면 원하는 value를 locked에 할당하겠다는 뜻이 됩니다.
이제 정상적으로 0이 출력되는 것을 볼 수 있습니다.
이번 글에서 Lock을 사용했을 때 발생하는 문제를 Interlocked.CompareExchange를 사용해서 해결하는 방법과 개념에 대해서 알아봤습니다.
'프로그래밍 > C#' 카테고리의 다른 글
[C#] 가변길이 매개변수 : params (0) | 2023.11.10 |
---|---|
[C#] 박싱과 언박싱 (0) | 2023.11.09 |
[C#] 멀티스레드 프로그래밍 : Monitor, DeadLock, Lock (0) | 2023.08.08 |
[C#] 멀티스레드 프로그래밍 : Interlocked (0) | 2023.08.08 |
객체지향 소프트웨어 공학 : 객체와 클래스의 개념 (0) | 2023.04.28 |