본문 바로가기
게임개발/게임서버

ReadWriteLock

by do_ng 2023. 10. 26.

개념

https://velog.io/@livelyjuseok/%EA%B0%9C%EC%9D%B8%EA%B3%B5%EB%B6%80-%EC%84%9C%EB%B2%84%EC%8B%A4%EC%8A%B512-ReaderWriterLock-%EA%B0%9C%EB%85%90

 

개인공부) 서버실습(14) - ReaderWriterLock 개념

지난 시간에 Auto, Manual, Mutex를 통한 Event 형식의 lock 구조에 대해 배웠다. Event 형식의 특징은 관리자(커널) 영역에 작업이 필요하기에Context Switching이 발생하여 구동이 느리기에 빠른 작업이 필요

velog.io

 

구현하기에 앞서 결정 할 규칙

1. 재귀적 락

2. 스핀락 정책(5000번 루프를 돌고도 Lock을 획득할 수 없으면 잠시 스핀락을 중단)

 

참고 : https://redisson.org/glossary/java-readwritelock.html

 

What is a Java ReadWriteLock? | Redisson

Locks are an essential feature for multithreaded programming, allowing different threads to operate on the same resources without causing bugs and race conditions.

redisson.org

 

코드

 

Lock에 사용될 상수, 변수

// 16진수 표현
// F(15) -> 이진수 1111, FFFF -> 이진수 1111 묶음 4개
const int EMPTY_FLAG = 0x00000000;
const int WRITE_MASK = 0x7FFF0000;         
const int READ_MASK = 0x0000FFFF; // 최대 2의 16승 개의 읽기 스레드 생성가능
const int MAX_SPIN_COUNT = 5000;

//  [Unused(1비트)] [WriteThreadId(15비트)] [ReadCount(16비트)]
// 초기값 : EMPTY_FLAG(LOCK 걸려있지 않는 상태)
public int _flag = EMPTY_FLAG;

 

 

WriteLock 함수

public void WriteLock() {
    // &(AND 비트 연산 수행) 두 개의 피연산자의 비트가 모두 1일 경우에만 1을 반환 
    // 현재 쓰레드 ID를 가져와서 현재 위치에서(1비트) 왼쪽으로 16비트만큼 밀어준다 
    // [1비트][15비트][16비트] -> 쓰레드 ID는 16비트 자리부터 공간을 차지함
    // WRITE_MASK의 비트가 [1비트 -> 0][15비트 -> 모두1] 이기 때문에 쓰레드 ID의 값만큼만 1로 표시            
    // WRITE_MASK의 비트                      -> [0][111 1111 1111 1111]
    // 쓰레드 ID -> 3(십진수) -> 11(이진수)       0  000 0000 0000 0011]
    //                                                               11]
    int temp = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;
    while (true)
    {
        for (int i = 0; i < MAX_SPIN_COUNT; i++) {
            // 첫번째 파라미터와 세번째 파라미터를 비교해서 동일하면 첫번째 파라미터 값을 두번째 파라미터로 변경
            // 아무도 writeLock을 점유하지 않은 상태(_flag의 값이 0)라면 Lock을 획득(_flag에 쓰레드 ID를 넣음)하고 WriteLock 함수 종료
            // "CompareExchange" 반환값이 변경하기 전의 값을 반환하므로 변경 전의 값이 "EMPTY_FLAG" 경우만 LOCK 획득 가능
            if (Interlocked.CompareExchange(ref _flag, temp, EMPTY_FLAG) == EMPTY_FLAG) {
                return;
            }                    
        }
        Thread.Yield();
    }
}

 

 

WriteLock 해제 함수

public void WriteUnLock() { 
    Interlocked.Exchange(ref _flag, EMPTY_FLAG);
}

 

쓰기 전용 Lock의 경우는 한 번에 오직 하나의 쓰레드만 접근이 가능하기 때문에 "_flag" 값을 0으로 밀어줌

(Lock(1) 또는 UnLock(0) 상태 총 2가지 경우만 존재)

 

 

ReadLock 함수

public void ReadLock() {
    // 아무도 WriteLock을 점유하고 있지 않으면 ReadCount를 1 늘림
    while (true)
    {
        for (int i = 0; i < MAX_SPIN_COUNT; i++)
        {
            // 쓰기 lock의 점유가 없는 경우에만 읽기 lock 획득 가능
            // 여러 개의 스레드가 동시에 읽기 lock을 점유하려고 해도 순차적으로 lock을 획득
            int expected = (_flag & READ_MASK);
            // ex) A와 B 스레드가 동시에 접근해서 expected 값이 0이라고 해도 
            // A 스레드가 먼저 "Interlocked" 함수를 수행하면 flag 값이 1로 변경되기 때문에 그 다음으로 들어온 B 스레드는 flag 값이 1이므로 if 조건에 틀려버림
            if (Interlocked.CompareExchange(ref _flag, expected + 1, expected) == expected)
            {
                return;                        
            }
        }               
    }
}

 

 

ReadLock 해제 함수

public void ReadUnLock()
{
    Interlocked.Decrement(ref _flag); 
}

동시에 여러 개의 쓰레드가 Read 작업을 할 수 있기 때문에 ReadLock을 획득한 쓰레드의 수를 감소

 

 

'게임개발 > 게임서버' 카테고리의 다른 글

멀티 쓰레드 프로그래밍  (0) 2024.04.17
멀티쓰레드 Lock 구현 방식  (2) 2024.01.15
소켓 프로그래밍  (0) 2023.10.29
TLS(Thread Local Storage)  (0) 2023.10.28
교착 상태(Deadlock)  (0) 2023.10.17