BLC에 사용하기 위해 약간 수정된 Loki 라이브러리
오리지널 Loki<library> 다운로드 링크:
http://loki-lib.sourceforge.net/index.php?n=Main.Download
※ Loki 라이브러리 라이선스:
MIT License
BLC에 사용하기 위해 약간 수정된 Loki 라이브러리는 BLC 라이브러리 압축파일에 포함되어 있습니다.
약간의 수정을 거쳐 BLC에 사용된 부분은 Loki 라이브러리의 일부분이고, 수정을 거치면서 기존 Loki 라이브러리의 다른 부분과는 호환되지 않는 부분이 있을 수 있습니다(특히,
"Functor.h", "SmallObj.h", "SmartPtr.h" 이 세 헤더는 확실하게 호환되지 않을 것입니다).
Modern C++ Design
의 Loki 라이브러리를, BLC 라이브러리에 사용하기 위해 C++17를 적용하여 약간 수정을 했습니다.
BLC에서는, Loki 라이브러리 중에서 "Threads.h"와 "Singleton.h", "TypeManip.h" 이
세 개의 헤더만 인클루드합니다. 해당 헤더의 구현 파일과, 헤더 내에서 인클루드하는, Loki 라이브러리의
다른 파일까지 모두 합치면 총 7개의 Loki 라이브러리 코드파일을 사용하게 됩니다.
변경된 파일들에 대해 유닛 테스트를 수행하긴 했으나, 미처 걸러지지 못한 버그가 있을 수 있습니다.
또한 Threads.h 헤더를 포함하는 기존 코드와 호환되지 않을 수 있습니다.
또한 실무 경험이 부족한 상태에서 짠 코드라 여러가지 미숙한 부분이 있을 수 있습니다. 이메일(ajw9105@hanmail.net)을 통해
개선해야 할 점이나 여러 의견들을 받고 있으니 관심 있으신 분들은 메일 보내주셨으면 좋겠습니다.
기반이 되는 소스 코드는
https://sourceforge.net/projects/loki-lib/files/Source%20Code/Modern%20C%2B%2B%20Design/
의 소스 코드 중 Reference 폴더 내의 소스 코드 입니다.
지금부터 변경된 헤더 파일 및 추가된 구현 파일에 대해서 설명하겠습니다. 여기서 설명하지 않은 Loki 라이브러리의 사용법은
Modern C++ Design
를 참고하세요.
차례
-
-
Singleton.h 헤더는 수정된 Threads.h 파일과 호환되도록 스레딩 단위전략 관련 부분만이 수정되었습니다.
기본적인 사용법은 기존의 Loki 라이브러리와 동일합니다.
-
이 헤더 파일이 가장 많은 변경사항이 있는 헤더 파일입니다. 수정된 점을 하나씩 짚어 봅시다.
-
우선 use_read_lock 이라는 비타입 템플릿 매개변수가 하나 추가되었습니다.
따라서 새로운 스레딩 단위전략 클래스 템플릿은 다음과 같이 선언됩니다.
스레딩 단위전략 클래스 템플릿의 선언 예제 코드 보기
- template <class Host, bool use_read_lock = true>
- class SingleThreaded;
-
- template <class Host, bool use_read_lock = true>
- class ObjectLevelLockable;
-
- template <class Host, bool use_read_lock = true>
- class ClassLevelLockable;
-
스레딩 단위전략 클래스 템플릿의 선언 예제 코드 접기
비타입 매개변수 use_read_lock이 true이고 ReadLock(조금 있다 설명합니다)을 사용하여 뮤텍스를 잠근 경우,
std::shared_mutex::lock_shared / std::shared_mutex::unlock_shared 멤버 함수를 통해
잠금/해제를 하여 공유 자원을 읽기만 하는 스레드가 여렷일 경우에 성능상 이점이 생기게 됩니다.
만일 ReadLock을 사용하여 뮤텍스를 잠궜더라도 use_read_lock이 false라면,
std::shared_mutex::lock / std::shared_mutex::unlock
멤버 함수를 사용하여 일반적인 잠금/해제를 수행하게 됩니다.
-
VolatileType, AtomicType 별칭 템플릿이 추가되었습니다. 이 별칭 템플릿으로 스레딩 단위전략 클래스 템플릿과
임의 타입 T(단, AtomicType의 경우 std::atomic의 템플릿 인자로 넘겨줄 수 있는 타입이어야 함)를 넘겨주면,
넘겨받은 스레딩 단위전략 클래스 템플릿에 따라 적절한 타입을 정해줍니다.
VolatileType, AtomicType 예제 코드 보기
- using Result0 = VolatileType<SingleThreaded, int>;
-
- using Result1 = VolatileType<ObjectLevelLockable, int>;
-
- using Result2 = VolatileType<ClassLevelLockable, int>;
-
-
- using Result3 = AtomicType<SingleThreaded, int>;
-
- using Result4 = AtomicType<ObjectLevelLockable, int>;
-
- using Result5 = AtomicType<ClassLevelLockable, int>;
-
VolatileType, AtomicType 예제 코드 접기
-
SafeLock 클래스 템플릿이 추가되었습니다. 이 클래스 템플릿은 세 종류의 특수화가 존재합니다.
첫 번째 특수화 버전은 템플릿 인자로 스레딩 단위전략 클래스 타입 하나를 받습니다.
그리고 생성자에서, 템플릿 인자로 받은 스레딩 단위전략 클래스를 상속받은 Host 클래스의 객체 하나를 받아
그 객체의 std::shared_mutex를 잠금/해제 합니다.
만일 템플릿 인자로 받은 스레딩 단위전략 클래스 타입이 상수 타입인 경우에는,
SafeLock의 생성자로 받을 수 있는 Host 클래스의 객체는 반드시 상수가 되어야 합니다.
이 때, use_read_lock이 true인 경우에는 잠금/해제 시
std::shared_mutex::lock_shared / std::shared_mutex::unlock_shared
멤버 함수가 사용됩니다.
SafeLock 예제 코드 보기
SafeLock의 두 번째 특수화 버전은 ClassLevelLockable 단위전략 클래스 템플릿 사용 시
static 함수에서 SafeLock을 사용하는 경우(SafeLock에 넘겨줄 객체가 없는 경우)에
사용할 수 있도록 만들어졌습니다. 이 특수화 버전은 템플릿 인수로 NullType을 받고, 생성자에 직접
사용할 shared_mutex와 읽기 잠금 사용 여부를 넘겨줘야 합니다.
그러나 ClassLevelLockable의 std::shared_mutex는 private 영역에 숨겨져 있으므로,
여러분이 직접 이 특수화 버전을 사용할 일은 거의 없을 것입니다. 대신 ClassLevelLockable의 public 영역에
선언된 StaticWriteLock과 StaticReadLock의 내부에서 사용되었으므로, 이 두 타입을 사용하여 간접적으로 사용할 수 있습니다.
한 가지 주의할 점은 StaticReadLock의 생성자는 객체를 받지 않으므로, 상수와 읽기 잠금에 대한 컴파일 오류를
받을 수 없다는 것입니다.
StaticLock 예제 코드 보기
-
- template <template <class, bool> class ThreadingModel>
- class Host : public ThreadingModel<Host, true>
- {
- public:
-
-
-
- using ThreadingModel::StaticWriteLock;
- using ThreadingModel::StaticReadLock;
-
-
-
- static void FunctionWrite() {
-
-
- StaticWriteLock lock;
-
- ...
-
- }
-
-
-
-
- static void FunctionRead() {
-
-
- StaticReadLock lock;
-
-
-
-
-
-
-
-
- ...
-
- }
-
-
- ...
- };
-
-
-
-
-
-
- template <template <class, bool> class ThreadingModel>
- class Host : public ThreadingModel<Host, false>
- {
- public:
-
- ...
-
- static void FunctionRead() {
- StaticReadLock lock;
-
-
-
-
- ...
-
- }
-
- ...
- };
StaticLock 예제 코드 접기
SafeLock의 마지막 특수화 버전은 첫 번째 특수화 타입의 목록(list) 버전입니다. 즉, 이 특수화 버전은 템플릿 인자로 스레딩 단위전략 클래스 타입들이 담긴
Typelist 하나를 받습니다. 그리고 생성자에서, 템플릿 인자로 받았던 스레딩 단위전략 클래스들을 각각 상속받은 Host 클래스들의 객체를 하나씩 받아
그 객체들의 std::shared_mutex를 잠금/해제 합니다. 이 때 std::shared_mutex의 주소(포인터)순으로 정렬하여 잠금/해제를 수행하므로,
데드락 걱정 없이 스레딩 단위전략을 사용하는 객체 여렷을 한꺼번에 잠금/해제 할 수 있습니다.
첫 번째 특수화 버전과 마찬가지로, 템플릿 인자로 받은 스레딩 단위전략 클래스 타입이 상수(const) 타입이었다면 그 스레딩 단위전략 클래스를 상속받은 Host 클래스의
객체(SafeLock의 생성자로 넘겨줄)도 상수여야 합니다. 그렇지 않다면 컴파일 오류를 발생시키는 것도 같습니다.
SafeLock 예제 코드 보기
- template <template <class, bool> class ThreadingModel>
- class Host1 : public ThreadingModel<Host1, true>
- {
- public:
-
-
-
- using ThreadingModel::ThisThreadingModel;
-
- ...
- };
-
- template <template <class, bool> class ThreadingModel>
- class Host2 : public ThreadingModel<Host2, true>
- {
- public:
- using ThreadingModel::ThisThreadingModel;
-
- ...
- };
-
- using TM1 = Host1::ThisThreadingModel;
- using TM2 = Host2::ThisThreadingModel;
-
- using WriteReadLock = SafeLock<TL::MakeTypelist_t<TM1, const TM2>>;
-
- ...
-
- Host1 host1;
- const Host2 host2;
-
- {
-
-
-
-
- WriteReadLock lock(host1, host2);
-
-
-
-
-
- ...
-
- }
-
SafeLock 예제 코드 접기
-
자주 사용될 만한 SafeLock의 인스턴스 선언이, 각 스레딩 단위전략 클래스 템플릿의 public 영역에 추가되었습니다.
예제 코드 보기
-
- template <class Host, bool use_read_lock>
- class SingleThreaded
- {
- ...
-
- public:
-
- ...
-
- using ThisThreadingModel = SingleThreaded;
-
-
- struct StaticLock final : private Private::Uncopyable
- {
- StaticLock() noexcept = default;
- explicit StaticLock(const SingleThreaded&) noexcept {}
- };
-
- using StaticWriteLock = StaticLock;
- using StaticReadLock = StaticLock;
- using Lock = StaticLock;
-
- using WriteLock = SafeLock<SingleThreaded>;
- using ReadLock = SafeLock<const SingleThreaded>;
-
-
- using WriteWriteLock = SafeLock<TL::MakeTypelist_t<SingleThreaded, SingleThreaded>>;
- using WriteReadLock = SafeLock<TL::MakeTypelist_t<SingleThreaded, const SingleThreaded>>;
- using ReadReadLock = SafeLock<TL::MakeTypelist_t<const SingleThreaded, const SingleThreaded>>;
-
- ...
-
- };
-
-
- template <class Host, bool use_read_lock>
- class ObjectLevelLockable
- {
- ...
-
- public:
- using ThisThreadingModel = ObjectLevelLockable;
-
-
-
- using WriteLock = SafeLock<ObjectLevelLockable>;
- using ReadLock = SafeLock<const ObjectLevelLockable>;
- using Lock = WriteLock;
-
- using WriteWriteLock = SafeLock<TL::MakeTypelist_t<ObjectLevelLockable, ObjectLevelLockable>>;
- using WriteReadLock = SafeLock<TL::MakeTypelist_t<ObjectLevelLockable, const ObjectLevelLockable>>;
- using ReadReadLock = SafeLock<TL::MakeTypelist_t<const ObjectLevelLockable, const ObjectLevelLockable>>;
-
- ...
-
- };
-
-
- template <class Host, bool use_read_lock>
- class ClassLevelLockable
- {
- ...
-
- public:
- using ThisThreadingModel = ClassLevelLockable;
-
- template <bool read_lock>
- class StaticLock final : private Private::Uncopyable
- {
- public:
- StaticLock() noexcept : lock_(*ClassLevelLockable::mtx(), read_lock && use_read_lock) {}
- explicit StaticLock(const ClassLevelLockable&) noexcept : StaticLock() {}
-
- private:
- SafeLock<NullType> lock_;
- };
-
- using StaticWriteLock = StaticLock<false>;
- using StaticReadLock = StaticLock<true>;
- using Lock = StaticWriteLock;
-
- using WriteLock = SafeLock<ClassLevelLockable>;
- using ReadLock = SafeLock<const ClassLevelLockable>;
-
- using WriteWriteLock = SafeLock<TL::MakeTypelist_t<ClassLevelLockable, ClassLevelLockable>>;
- using WriteReadLock = SafeLock<TL::MakeTypelist_t<ClassLevelLockable, const ClassLevelLockable>>;
- using ReadReadLock = SafeLock<TL::MakeTypelist_t<const ClassLevelLockable, const ClassLevelLockable>>;
-
- ...
-
- };
-
-
-
-
- template <template <class, bool> class ThreadingModel>
- class Host : public ThreadingModel<Host, true>
- {
- ...
-
- public:
-
-
-
- static void StaticReadAndWrite(...) {
- StaticWriteLock lock;
-
- ...
- }
-
-
-
- static void StaticReadOnly(...) {
- StaticReadLock lock;
-
- ...
- }
-
-
- void NonConstMemFn(...) {
- WriteLock lock(*this);
-
- ...
- }
-
-
-
-
-
-
-
- void ConstMemFn(...) const {
- ReadLock lock(*this);
-
- ...
- }
-
-
-
-
- Host& operator =(const Host& rhs) & {
- WriteReadLock lock(*this, rhs);
-
- ...
- }
-
- ...
-
- };
-
-
-
-
-
- template <template <class, bool> class ThreadingModel>
- class Host1 : public ThreadingModel<Host1, true>
- { ... };
-
- template <template <class, bool> class ThreadingModel>
- class Host2 : public ThreadingModel<Host2, true>
- {
- ...
-
- public:
-
-
- Host2& operator =(const Host2& rhs) & {
- WriteReadLock lock(*this, rhs);
-
- ...
- }
-
-
- Host2& operator =(const Host1& rhs) & {
- WriteReadLock lock(*this, rhs);
-
- ...
- }
-
-
- Host2& operator =(const Host1& rhs) & {
- using TM2 = Host2::ThisThreadingModel;
- using TM1 = Host1::ThisThreadingModel;
- using WriteReadLockForDiffType = SafeLock<TL::MakeTypelist_t<TM2, const TM1>>;
-
- WriteReadLockForDiffType lock(*this, rhs);
-
- ...
- }
- };
예제 코드 접기
-
C++20에서 비권장으로 분류된 volatile관련 복합 대입 연산자들을 모두 수정하였습니다.
예제 코드 보기
-
- template <class Host, bool use_read_lock>
- class SingleThreaded
- {
- ...
-
- public:
-
- ...
-
- static IntType AtomicAdd(volatile IntType& lval, IntType val)
- { return lval += val; }
-
- static IntType AtomicSubtract(volatile IntType& lval, IntType val)
- { return lval -= val; }
-
- static IntType AtomicIncrement(volatile IntType& lval)
- { return ++lval; }
-
- static IntType AtomicDecrement(volatile IntType& lval)
- { return --lval; }
-
- ...
-
- };
-
-
-
- template <class Host, bool use_read_lock>
- class SingleThreaded
- {
- ...
-
- public:
-
- ...
-
- static IntType AtomicAdd(volatile IntType& lval, IntType val)
- { return lval = lval + val; }
-
- static IntType AtomicSubtract(volatile IntType& lval, IntType val)
- { return lval = lval - val; }
-
- static IntType AtomicIncrement(volatile IntType& lval)
- { return lval = lval + 1; }
-
- static IntType AtomicDecrement(volatile IntType& lval)
- { return lval = lval - 1; }
-
- ...
-
- };
예제 코드 접기
수정된 곳을 보면 멀티스레드 동기화가 전혀 되지 않는 코드란 것을 알 수 있지만, SingleThreaded 단위전략의 정적 멤버함수이기 때문에 문제가 없습니다.
-
마지막으로 원자적 정수 연산이 std::atomic<int>를 사용하도록 변경되었습니다.
std::atomic<int>는 복사 연산이 금지되어 있으므로, 원자적 연산 함수의 반환 타입과
매개변수 타입들이 값에 의한 전달에서 참조자(혹은 상수 참조자)에 의한 전달로 바뀌었습니다.
-
MakeTypelist가 가변인자 템플릿을 사용하여 템플릿 인자의 개수에 제한이 없도록 수정되었습니다.
또한 MakeTypelist를 비롯한 여러 템플릿들의 별칭 템플릿이 추가되었습니다.
MakeTypelist와 별칭 템플릿 예제 코드 보기
-
-
- using MyTypelist = typename MakeTypelist<T1, T2, T3, ...>::Result
-
-
-
- using MyTypelist = MakeTypelist_t<T1, T2, T3, ...>
MakeTypelist와 별칭 템플릿 예제 코드 접기
다음은 새로 추가된 별칭 템플릿의 목록입니다. 순서는 헤더에 선언된 순서를 따릅니다.
-
MakeTypelist_t
-
TypeAt_t
-
TypeAtNonStrict_t
-
Append_t
-
Erase_t
-
EraseAll_t
-
NoDuplicates_t
-
Replace_t
-
ReplaceAll_t
-
Reverse_t
-
MostDerived_t
-
DerivedToFront_t
별칭 템플릿에 대한 자세한 설명은
Effective Modern C++의
항목 9를 참고하세요.
-
Select 템플릿에 대한 별칭 템플릿 Select_t가 추가되었습니다.
별칭 템플릿을 사용하는 예제 코드는 Typelist.h 헤더 항목 에서 찾으실 수 있습니다.
그리고, 별칭 템플릿에 대한 자세한 설명은
Effective Modern C++의
항목 9를 참고하세요.
-
-
Threads.h 헤더에 추가된 SafeLock 클래스 템플릿과 관련된 코드에서, 코드 부풀림을 최소화 하기 위해 중복되는 코드를
함수로 만들어 cpp파일에 따로 넣었습니다. 직접 이 파일에 접근해야 할 일은 거의 없을 것이므로, 크게 신경쓰지 않아도 되는 부분입니다.