게임 서버 구현 과정에서 멀티 스레드를 사용하려다보니 특정 메모리 영역에는 동기화를 위한 작업이 필요하다.
그런데 무작정 락을 걸자니 이래저래 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 |
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 |
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 |
댓글 없음:
댓글 쓰기