Глобальное государство Mutex vs Lock - PullRequest
0 голосов
/ 16 ноября 2018

Этот вопрос не относится к glfw, но он хорошо описывает то, что я имею в виду. В glfw, чтобы начать использовать любые функции, нам нужно вызвать glfwInit (), и когда нам больше не нужно их использовать, мы вызываем glfwTerminate (), я пытался придумать способ обернуть это вокруг класса RAII и я нашел два полезных способа сделать это, но я не уверен в плюсах и минусах каждого из них. Во всех этих случаях я опускаю проверку ошибок и тому подобное, поскольку они не слишком изменят примеры.

1: использование класса блокировки

Моей первой идеей было создать класс блокировки, который вызывал бы glfwInit () в начале его жизни и glfwTerminate () и в конце, что-то вроде этого:

struct GLFWLock
{
     GLFWLock() { glfwInit(); }
    ~GLFWLock() { glfwTerminate(); }
}

Я понял, что если два из этих классов будут созданы, то glfwInit и glfwTerminate будут вызываться дважды, поэтому я добавил счетчик ссылок, и я чувствовал, что это было довольно полное решение, за исключением того, что оно было поточно-ориентированным и, возможно, другими , но по сути это было бы то же самое:

struct GLFWLock
{
    static size_t ref_count; /* = 0 in .cpp */

     GLFWLock() { if ( ref_count == 0 ) { glfwInit(); } ref_count++; }
    ~GLFWLock() { ref_count--; if ( ref_count == 0 ) { glfwTerminate(); } }
}

2: Использование Mutex-подобного класса

Поработав с предыдущей моделью немного, я понял, что это то же самое, что и std :: lock_guard с мьютексом, поэтому я подумал, что могу создать класс мьютекса и заставить пользователя всякий раз делать lock_guard им нужно было использовать glfw вместо того, чтобы предлагать просто блокировку.

В итоге я придумал это, что в некоторой степени соответствует концепции мьютекса в соответствии со стандартом, игнорируя некоторые формальные требования и сосредоточившись на том, что на самом деле будет использовать std :: lock_guard:

struct GLFWMutex
{
    static size_t ref_count; /* = 0 in the .cpp */
    bool locked = false;

    ~GLFWMutex() { unlock(); }

    void lock() 
    {
        if ( !locked )
        {
            if ( ref_count == 0 ) { glfwInit(); }
            ref_count++;
            locked = true;
        }
    }

    void unlock()
    {
        if ( locked )
        {
            locked = false;
            ref_count--;
            if ( ref_count == 0 ) { glfwTerminate(); }
        }
    }
};

А затем используйте его с std :: lock_guard, когда это необходимо, как обычный мьютекс.

Я вижу, что использование подобного замку класса требует меньше ввода, поскольку вам не нужно объявлять мьютекс и охрану, но будет ли мьютексоподобный класс более полезным? Возможно, после добавления дополнительных функций-членов, таких как try_lock (), owns_lock () и других? Или есть лучшее решение для инкапсуляции этого поведения?

Редактировать 1:

Синтаксис использования, который я хотел бы для этого общего состояния, будет выглядеть примерно так:

struct glfw_shared_state
{
    static size_t ref_count; /* = 0 in .cpp */

     glfw_shared_state() { if ( ref_count == 0 ) { glfwInit(); } ref_count++; }
    ~glfw_shared_state() { ref_count--; if ( ref_count == 0 ) { glfwTerminate(); } }
};

struct Game
{
    /// While this Game object is alive, I want to share the state of glfw, so it isn't terminated
    glfw_shared_state state;

    (...)
};

Где каждый экземпляр игры будет увеличивать ref_count на единицу, что заставит glfw остаться в живых на протяжении всей жизни игры, в основном shared_ptr, но для функций вместо объекта

Редактировать 2:

Что касается std :: lock_guard, я имел в виду что-то вроде следующего:

/// This has an internal ref counter for how many threads are currently locking it
/// When it first starts with 0 threads and someone locks, it calls glfwInit()
/// Then everytime some other thread locks, it just ups the ref counter
/// After every thread using it has unlocked it and it's ref counter is 0, it calls glfwTerminate()
/// So this isn't locking anyway, it's just sharing a state
glfw_mutex global_glfw_mutex;

void draw()
{
    /// Make sure glfw is alive during this draw function
    std::lock_guard lock(global_glfw_mutex);
}

Это немного запутанно, но по сути это то, что я имел в виду со вторым примером в оригинальном посте. Я думаю, что мьютекс и блокировка не подходят для этого, но я думаю, что комментарий передает значение, которое я хочу, чтобы код есть, блокировка - это просто shared_ptr

...