Безопасно ли использовать фиктивную ложную блокировку с std :: condition_variable_any? - PullRequest
0 голосов
/ 04 мая 2018

Я ищу что-то вроде wait_queue_t в ядре Linux. Моя базовая структура данных, связанная с синхронизацией, не содержит блокировок, поэтому не нуждается в защите мьютекса.

Приобретение std::mutex только для того, чтобы использовать std::condition_variable для блокировки ожидания, кажется, приводит к ненужным издержкам.

Я знаю, что есть futex для Linux и WaitOnAddress для Windows, но меня больше интересуют языковые стандартные вещи здесь.

Согласно cppreference wiki std::condition_variable_any может работать с любым пользовательским Lockable.

Так что, если я использую его с фиктивным поддельным замком, как показано ниже:

class FakeLock
{
public:
    inline void lock() {}
    inline void unlock() {}
    inline bool try_lock() { return true; }
};

template<typename WaitLock=FakeLock>
class WaitQueue
{
private:
    WaitLock m_lock;
    std::condition_variable_any m_cond;

public:
    inline void wait()
    {   
        std::unique_lock<WaitLock> lock(m_lock);

        m_cond.wait(lock);
    }   

    inline void notify_one()
    {   
        m_cond.notify_one();
    }   

    inline void notify_all()
    {   
        m_cond.notify_all();
    }   
};

Существует ли потенциальный риск непредвиденного поведения при использовании WaitQueue выше при использовании ядра Linux wait_queue_t?

1 Ответ

0 голосов
/ 04 мая 2018

Думая о проблеме снова, я думаю, что получил ответ.

Учитывая класс LockFreeStack, который является стеком без блокировки.

рассмотрите следующий код:

WaitQueue wq;
LockFreeStack<std::string> stack;

std::thread t1([&]() {
    while (true) {
         // sleep for some time
         stack.push(/* random string */);          // (1.1)
         wq.notify_one();                          // (1.2)
    }
});

std::thread t2([&]() {
    while (true) {
         if (stack.empty())                        // (2.1)
             wq.wait();                            // (2.2)
         auto str = stack.pop();
         // print string
    }
});

Без защиты wait_queue / условной переменной с реальной блокировкой возможно выполнение двух потоков в следующей последовательности:

(2.1)
(1.1)
(1.2)
(2.2)

Создание темы t2 отлично пропустит последнее обновление от t1. t2 сможет возобновить выполнение только после следующего вызова notify_one().

Реальная блокировка необходима, чтобы гарантировать, что условная переменная изменяется атомарно с фактическими данными / состоянием интереса.

...