Recursive SpinLock, ReaderWriterLock 구현 연습
- 재귀적Recursive 락을 허용하거나 안하거나 하는 결정
- 스핀락 정책. “몇 번 스핀락 루프를 돌고난 후에는 Yield를 한다.”
새롭게 작성하는 C# 클래스 스크립트는 Lock.cs입니다.
지난번 spinLock클래스에는 소유권을 획득하는 Acquire() 메소드, 소유권을 내려놓는 Release() 메소드가 있었습니다. 이번에도 동일한 역할을 하는 writeLock()메소드, unwriteLock()메소드가 있습니다. 다만 여기에서 몇 번 스핀락 루프를 돌았는지 개수를 세어 주게 됩니다. 일단, 재귀적 락은 허용하지 않습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Lock.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerCore
{
// 재귀적 락을 허용할지 (No)
// 스핀락 정책 (5000번 -> Yield)
internal class Lock
{
const int EMPTY_FLAG = 0x00000000; // (32비트x)
const int WRITE_MASK = 0x7FFF0000; // (1비트x), 15비트o, (16비트x)
const int READ_MASK = 0x0000FFFF; // (16비트x), 16비트o
const int MAX_SPIN_COUNT = 5000;
// [Unused(1)] [WriteThreadId(15비트)] [ReadCount(16비트)]
int _flag = EMPTY_FLAG;
public void WriteLock()
{
// 아무도 WriteLock or ReadLock을 획득하고 있지 않을 때, 경합해서 소유권을 얻는다
// 16비트만큼 밀어주니까 총 32비트가 되고, WRITE_MASK를 적용하면, [WriteThreadId(15비트)] 부분만 살아남음.
int desired = (Thread.CurrentThread.ManagedThreadId 1)가 1로 바꿔놨으니, 다음번에 와서 B(0->1)를 원하는 B는 실행할 수가 없음.
return;
}
Thread.Yield();
}
}
public void ReadUnlock()
{
Interlocked.Decrement(ref _flag);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//Program.cs
using System;
using System.Data;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
internal class Program
{
static volatile int count = 0;
static Lock _lock = new Lock();
static void Main(string[] args)
{
Task t1 = new Task(delegate ()
{
for (int i = 0; i < 100000; i++)
{
_lock.WriteLock();
count++;
_lock.WriteUnlock();
}
});
Task t2 = new Task(delegate ()
{
for (int i = 0; i < 100000; i++)
{
_lock.WriteLock();
count--;
_lock.WriteUnlock();
}
});
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(count);
}
}
}
잘 동작합니다. 그럼 재귀적으로 Program.cs만 다음과 같이 두 번 Lock을 해주도록 변경해서 실행해봅시다.
1
2
3
4
5
6
7
8
for (int i = 0; i < 100000; i++)
{
_lock.WriteLock();
_lock.WriteLock();
count++;
_lock.WriteUnlock();
_lock.WriteUnlock();
}
while문에 갇혀버렸군요.
재귀적 락을 허용해봅시다. Lock.cs만 다음과 같이 바꿉니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//Lock.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerCore
{
// 재귀적 락을 허용할지 (Yes)
// WriteLock->WriteLock OK / WriteLock->ReadLock OK,
// ReadLock->WriteLock No
// 스핀락 정책 (5000번 -> Yield)
internal class Lock
{
const int EMPTY_FLAG = 0x00000000; // (32비트x)
const int WRITE_MASK = 0x7FFF0000; // (1비트x), 15비트o, (16비트x)
const int READ_MASK = 0x0000FFFF; // (16비트x), 16비트o
const int MAX_SPIN_COUNT = 5000;
// [Unused(1)] [WriteThreadId(15비트)] [ReadCount(16비트)]
int _flag = EMPTY_FLAG;
int _writeCount = 0; //재귀적으로 몇개의 락을 할지를 관리.
public void WriteLock()
{
// 특수한 경우. 동일 쓰레드가 WriteLock을 이미 획득하고 있는지 확인
int lockThreadId = (_flag & WRITE_MASK) >> 16; // 0x00007FFF
if (Thread.CurrentThread.ManagedThreadId == lockThreadId)
{
_writeCount++;
return;
}
// 아무도 WriteLock or ReadLock을 획득하고 있지 않을 때, 경합해서 소유권을 얻는다
// 16비트만큼 밀어주니까 총 32비트가 되고, WRITE_MASK를 적용하면, [WriteThreadId(15비트)] 부분만 살아남음.
int desired = (Thread.CurrentThread.ManagedThreadId > 16; // 0x00007FFF
if (Thread.CurrentThread.ManagedThreadId == lockThreadId)
{
Interlocked.Increment(ref _flag);
return;
}
// 아무도 WriteLock을 획득하고 있지 않으면, ReadCount를 1 늘린다.
while (true)
{
for (int i = 0; i 1)가 1로 바꿔놨으니, 다음번에 와서 B(0->1)를 원하는 B는 실행할 수가 없음.
return;
}
Thread.Yield();
}
}
public void ReadUnlock()
{
Interlocked.Decrement(ref _flag);
}
}
}
재귀적으로 잘 빠져나왔습니다. 그렇다면 마지막으로, 만약 lock과 unlock의 짝이 맞지 않는다면?
1
2
3
4
5
6
7
for (int i = 0; i < 100000; i++)
{
_lock.WriteLock();
_lock.WriteLock();
count++;
_lock.WriteUnlock();
}
짝이 안 맞으면 못 빠져나오게 됩니다. 락을 안 푸니까 풀릴 때까지 기다리기 때문입니다.
이 글은 인프런의 '[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버' 강의를 참고하여 정리 목적으로 작성하였습니다. 감사합니다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.




