싱글 스레드 조건에서 가졌던 마인드는 버리자!
무슨 말이냐. 아래 코드는 문제가 있다는 것이다.
namespace ServerCore;
internal class Program
{
private static int number;
private static void Thread1()
{
for (var i = 0; i < 1000000; i++)
{
var a = number;
Interlocked.Increment(ref number);
var b = number;
Console.WriteLine($"{a} -> {b}");
}
}
private static void Thread2()
{
for (var i = 0; i < 1000000; i++)
Interlocked.Decrement(ref number);
}
private static void Main(string[] args)
{
var t1 = new Task(Thread1);
var t2 = new Task(Thread2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(number);
}
}
number를 저런 식으로 빼는 순간, 다시 경합 조건이 발생해서 정확하지 않은 값을 읽을 수 있다는 것.
애초에 저렇게 값을 복사하는 일은 없어야 한다.
문제 해결의 핵심은 number
변수에 대한 동시 접근 시 정확성을 보장하기 위해 Interlocked
의 반환값을 사용하는 것이다. Interlocked
메서드를 사용할 때는 반환값을 사용해야 정확한 결과를 얻을 수 있다.
문제점 요약
number
를var a = number;
로 읽을 때, 다른 스레드가 값을 변경할 수 있어 정확하지 않다.Interlocked.Increment
나Interlocked.Decrement
의 반환값을 사용하지 않으면, 원자적 연산 결과를 놓칠 수 있다.
수정된 코드
number
의 증감 연산 후 값을 정확히 출력하려면 Interlocked
의 반환값을 변수에 저장해야 한다.
namespace ServerCore
{
internal class Program
{
private static int number;
private static void Thread1()
{
for (var i = 0; i < 1000000; i++)
{
var b = Interlocked.Increment(ref number); // 반환값을 사용하여 정확한 값 보장
Console.WriteLine($"Incremented to {b}");
}
}
private static void Thread2()
{
for (var i = 0; i < 1000000; i++)
{
var b = Interlocked.Decrement(ref number); // 반환값을 사용하여 정확한 값 보장
Console.WriteLine($"Decremented to {b}");
}
}
private static void Main(string[] args)
{
var t1 = new Task(Thread1);
var t2 = new Task(Thread2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine($"Final value: {number}");
}
}
}
Interlocked.Increment
와Interlocked.Decrement
의 반환값을 바로 사용하여 연산 후 정확한 값을 출력한다.- 이로써 다중 스레드 환경에서도 정확한 결과를 보장할 수 있다.
'공부 > 게임서버' 카테고리의 다른 글
컨텍스트 스위칭 (0) | 2024.11.10 |
---|---|
교착 상태 (Deadlock) (1) | 2024.11.09 |
하드웨어 최적화 문제 / 메모리 배리어 (3) | 2024.11.09 |
컴파일러 최적화에 따른 문제 (0) | 2024.11.09 |
TaskCreationOptions.LongRunning 사용법과 주의사항 (0) | 2024.11.09 |