요약

Lock

std::mutex 는 (현시점에서)VisualC++ Runtime에서의 구현은 SRWLOCK로 구현하여, Kernel Object의 HANDLE Mutex나 CRITICAL_SECTION 와도 별개가 된다. 진짜 맞는지 조사 해본다.

조사 코드

#include <iostream>
#include <atomic>
#include <mutex>
#include <vector>
#include <memory>
#include <Windows.h>

template<typename Out>
void print_env(Out& out)
{
#define PRINT_MACRO(VAR) out << #VAR ": " << VAR << std::endl
    PRINT_MACRO(_MSC_FULL_VER);
    PRINT_MACRO(_MSVC_LANG);
    PRINT_MACRO(_WIN64);
    PRINT_MACRO(_MT);
#ifdef _DEBUG
    out << "with _DEBUG" << std::endl;
#else
    out << "without _DEBUG" << std::endl;
#endif
    PRINT_MACRO(_DLL);
#undef PRINT_MACRO
}

void exec_systeminfo()
{
    system("systeminfo | findstr /r \"^OS.버전\"");
    system("systeminfo | findstr /r \"^시스템 종류\"");
}

using namespace std;

template<typename T>
size_t lock_loop(atomic_bool& stop, T& lockable)
{
    size_t count = 0;
    while (!stop)
    {
        lock_guard<T> l(lockable);
        if (++count == 0)
        {
            throw runtime_error("count overflow");
        }
    }
    return count;
}

struct my_critical_section
{
    CRITICAL_SECTION cs;
    my_critical_section()
    {
        ::InitializeCriticalSection(&cs);
    }
    ~my_critical_section()
    {
        ::DeleteCriticalSection(&cs);
    }
    void lock()
    {
        ::EnterCriticalSection(&cs);
    }
    void unlock()
    {
        ::LeaveCriticalSection(&cs);
    }
};

struct my_mutex
{
    HANDLE handle;
    my_mutex() : handle(::CreateMutex(NULL, FALSE, NULL))
    {
    }
    ~my_mutex()
    {
        if (handle != NULL)
        {
            ::CloseHandle(handle);
        }
    }
    void lock()
    {
        ::WaitForSingleObject(handle, INFINITE);
    }
    void unlock()
    {
        ::ReleaseMutex(handle);
    }
};

struct my_srwlock
{
    SRWLOCK srwlock;
    my_srwlock()
    {
        InitializeSRWLock(&srwlock);
    }
    ~my_srwlock()
    {
    }
    void lock()
    {
        ::AcquireSRWLockExclusive(&srwlock);
    }
    void unlock()
    {
        ::ReleaseSRWLockExclusive(&srwlock);
    }
};

template<typename T>
size_t lock_loop_mt(atomic_bool& stop, T& lockable, size_t C = 1)
{
    vector<unique_ptr<thread>> threads;
    vector<size_t> result(C);
    for (size_t i = 0; i < C; ++i)
    {
        threads.emplace_back(std::make_unique<thread>([&, i]
        {
            result[i] = lock_loop(stop, lockable);
        }));
    }
    for (auto& pt : threads)
    {
        pt->join();
    }
    size_t sum = 0;
    for (auto x : result)
    {
        size_t new_sum = sum + x;
        if (sum > new_sum)
        {
            throw runtime_error("count overflow");
        }
        sum = new_sum;
    }
    return sum;
}

template<typename T>
void test(const char* prefix)
{
    SYSTEM_INFO info;
    ::GetSystemInfo(&info);
    for (int i = 1; i < static_cast<int>(info.dwNumberOfProcessors); ++i)
    {
        T m;
        atomic_bool stop = false;
        size_t result = 0;
        thread t([&]
        {
            result = lock_loop_mt<T>(stop, m, i + 1);
        });
        this_thread::sleep_for(3s);
        stop = true;
        t.join();
        cout << prefix << i + 1 << "," << result << endl;
    }
}

int main()
{
    print_env(std::cout);
    exec_systeminfo();
    cout << "method,threads,count" << endl;
    test<mutex>("std::mutex,");
    test<my_critical_section>("my_critical_section,");
    test<my_mutex>("my_mutex,");
    test<my_srwlock>("my_srwlock,");
    return 0;
}

측정 내용

lock을 실시하기 위한 동기 오브젝트를 3초간 복수 thread로부터 lock/unlock를 반복하고, 합계 lock/unlock 횟수를 계측한다. 3초 sleep 하고 멈추는 로직이므로 오차는 나름대로라고 생각한다. 동기화 객체는 아래 4개이다.

실행 결과

VS2019 와 2022가 기준이다.

VC++2019