2018년 11월 4일 일요일

Read Lock, Write Lock 그리고 shared_mutex

게임 서버 구현 과정에서 멀티 스레드를 사용하려다보니 특정 메모리 영역에는 동기화를 위한 작업이 필요하다.


그런데 무작정 락을 걸자니 이래저래 mutex를 남발하면 멀티 스레드가 무슨 소용인가.. 커널 왔다갔다하면서 비용이 많이 들텐데..


CRITICAL_SECTION으로 하면 유저 영역에서 해결이 되긴 한다는데...



뭐 어쨋든 락을 가능한 적게 걸기 위해, Read Lock, Write Lock 방법을 생각했는데


이 방식을 간략하게 설명하자면 아래와 같다.

"해당 영역에 변화를 주지 않는다면 여러 스레드가 접근해도 좋다"


Read Lock

 - Read하는 스레드끼리는 모두 읽어도 된다. 데이터를 변화시키지만 않는다면.

   단, 성능적인 측면을 위해 Write를 대기하는 스레드가 있다면 추가적으로 Read하는 스레드를 허용하지 않는다.


Write Lock

 - Write하는 스레드가 있을 경우, Read 스레드도 제한하고 다른 Write 스레드도 제한한다.




음.. 일단 내가 고려한 방식은 크게 2가지다. 2가지 측면을 고려하면서 성능적인 측면에서 아래와 같은 궁금증이 생겼는데


아직 테스트는 못 해봤다. 우선은 정리 먼저 해두고 테스트 코드 만들어서 해봐야지. 평일은 안 되겠고 주말에...


mutex를 사용하면 커널 영역에서 이루어지니까 2번 방법인 Interlock 함수를 이용하는 것보다 비용이 비싸지 않을까?


첫 번째는 C++11 에서 포함된 shared_mutex, unique_lock, shared_lock을 사용하는 것이다.


Read Lock을 하는 경우에는 아래처럼 사용한다.


1
shared_lock<shared_mutex> lock(mutex);
cs


그리고 Write Lock을 하는 경우에는 아래처럼 사용한다.


1
unique_lock<shared_mutex> lock(mutex);
cs


두 번째 방법으로는 Interlock 함수를 사용하는 방식이다. 
ReadWriteLock.h
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
#pragma once
#include <Windows.h>
 
class ReadWriteLock
{
public:
    ReadWriteLock();
    ~ReadWriteLock();
 
    ReadWriteLock(const ReadWriteLock& rhs) = delete;
    ReadWriteLock& operator=(const ReadWriteLock& rhs) = delete;
 
    // exclusive mode
    void EnterWriteLock();
    void LeaveWriteLock();
 
    // share mode
    void EnterReadLock();
    void LeaveReadLock();
 
    long GetLockFlag() const { return lock_flag_; }
 
 
private:
    enum LockFlag
    {
        LOCK_FLAG_WRITE_MASK = 0x7FF00000,
        LOCK_FLAG_WRITE_FLAG = 0x00100000,
        LOCK_FLAG_READ_MASK  = 0x000FFFFF  // 하위 20비트를 readlock을 위한 플래그로 사용한다.
    };
 
    volatile long lock_flag_;
};
cs

ReadWriteLock.cpp
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
60
61
62
63
64
65
66
67
68
69
#include "pch.h"
#include "ReadWriteLock.h"
 
ReadWriteLock::ReadWriteLock()
{
}
 
ReadWriteLock::~ReadWriteLock()
{
}
 
// exclusive mode
void ReadWriteLock::EnterWriteLock()
{
    while (true)
    {
        // 다른 스레드가 write lock 풀어줄 때까지 기다린다.
        while (lock_flag_ & LOCK_FLAG_WRITE_MASK)
        {
            YieldProcessor();
        }
 
        if (LOCK_FLAG_WRITE_FLAG == (InterlockedAdd(&lock_flag_, LOCK_FLAG_WRITE_FLAG) & LOCK_FLAG_WRITE_MASK))
        {
            // 다른 스레드가 read lock 풀어줄 때까지 기다린다.
            while (lock_flag_ & LOCK_FLAG_READ_MASK)
            {
                YieldProcessor();
            }
 
            return;
        }
        InterlockedAdd(&lock_flag_, -LOCK_FLAG_WRITE_FLAG);
    }
}
 
void ReadWriteLock::LeaveWriteLock()
{
    InterlockedAdd(&lock_flag_, -LOCK_FLAG_WRITE_FLAG);
}
 
// share mode
void ReadWriteLock::EnterReadLock()
{
    while (true)
    {
        // wait for release write lock
        while (lock_flag_ & LOCK_FLAG_WRITE_MASK)
        {
            YieldProcessor();
        }
 
        // check write lock
        if ((0 == InterlockedIncrement(&lock_flag_) & LOCK_FLAG_WRITE_MASK))
        {
            return;
        }
        else
        {
            InterlockedDecrement(&lock_flag_);
        }
    }
}
 
void ReadWriteLock::LeaveReadLock()
{
    InterlockedDecrement(&lock_flag_);
}
 
cs


댓글 없음:

댓글 쓰기

A*, JPS 길찾기 알고리즘 시뮬레이션 사이트

https://qiao.github.io/PathFinding.js/visual/ 길 찾기 알고리즘 시행 과정을 보여주는 사이트다. 링크 메모..