Как правильно использовать атомарный тип с condition_variable, чтобы избежать блокировки во время спокойного атомарного чтения

#c 11 #atomic #condition-variable #memory-barriers #stdatomic

#c 11 #атомарный #условие-переменная #барьеры памяти #stdatomic

Вопрос:

В случае общего счетчика, который можно ожидать (с condition_variable) Интересно, что лучше в отношении средства получения количества между использованием атомарного типа для подсчета (mt_counter_B impl) или блокировкой (mt_counter_A impl)? Кроме того, являются ли memory_orders, которые я использовал, правильными? Я намерен использовать count_relaxed() для пользовательского интерфейса без состояния, поэтому мне не нужно, чтобы он был безопасным для порядка.

 class mt_counter_A
{
protected:
    std::condition_variable cv;
    std::mutex cv_m;
    uint64_t cnt;

public:
    mt_counter_Aamp; operator  () {
        {
            std::lock_guard<std::mutex> lk(cv_m);
              cnt;
        }
        cv.notify_all();
    }

    uint64_t count() {
        std::lock_guard<std::mutex> lk(cv_m);
        return cnt;
    }

    void wait_until(uint64_t count) {
        std::unique_lock<std::mutex> lk(cv_m);
        cv.wait(lk, [amp;] { return cnt >= count; });
    }
};

class mt_counter_B
{
protected:
    std::condition_variable cv;
    std::mutex cv_m;
    std::atomic_uint_fast64_t cnt;

public:
    mt_counter_Bamp; operator  () {
        {
            std::lock_guard<std::mutex> lk(cv_m);
            cnt.fetch_add(std::memory_order_relaxed);
        }
        cv.notify_all();
    }

    uint64_t count(std::memory_order order = std::memory_order_seq_cst) {
        return cnt.load(order);
    }

    uint64_t count_relaxed() {
        return cnt.load(std::memory_order_relaxed);
    }

    void wait_until(uint64_t count) {
        std::unique_lock<std::mutex> lk(cv_m);
        cv.wait(lk, [amp;]{ return cnt.load(std::memory_order_relaxed) >= count; });
    }
};
  

Комментарии:

1. CV не предназначены для использования без блокировки.

2. Я понимаю это, но что касается синхронизированного значения (здесь cnt ), если это атомарный тип без блокировки, это добавило бы ненужный уровень блокировки.. Итак, если я хочу получить доступ к этому значению небезопасно, но с атомарностью, лучше ли просто использовать блокировку, связанную с использованием condition_var, или использовать atomic memory_orders, которые я предложил?

3. Какую «безопасность» вы согласны отказаться? Кроме того, почему вы хотите использовать CV в первую очередь?

4. Я бы использовал связанный список двоичных семафоров с пользовательской логикой пробуждения.

5. Оба подхода кажутся правильными. «Интересно, что лучше всего подходит для получения количества …» — Универсального «лучшего» в программировании не существует. Скорее всего, наилучшая производительность будет достигнута при подходе B (считывание счетчика без мьютекса). Но с точки зрения поддержания кода подход A (который позволяет избежать атомарных переменных) может дать гораздо больше, чем повышение производительности функции, которая вызывается только из потока GUI: в этом случае функция лучше быть быстрой, но она не должна быть чрезвычайно быстрой.