И странное использование условной переменной с локальным мьютексом

#c #multithreading #qt #qmutex

#c #многопоточность #qt #qmutex

Вопрос:

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

 template < typename _Msg>
class WaitQue: public QWaitCondition 
{
public:
    typedef _Msg  DataType;

    void wakeOne(const DataTypeamp; msg) 
    {
        QMutexLocker lock_(amp;mx);
        que.push(msg);
        QWaitCondition::wakeOne(); 
    }
    
    void wait(DataTypeamp; msg)
    {
        /// wait if empty.
        {
            QMutex wx;  // WHAT?
            QMutexLocker cvlock_(amp;wx);
            if (que.empty()) 
                QWaitCondition::wait(amp;wx);  
        }

        {
            QMutexLocker _wlock(amp;mx);
            msg = que.front();            
            que.pop();
        }
    }

    unsigned long size() { 
        QMutexLocker lock_(amp;mx); 
        return que.size();
    }

private:
    std::queue<DataType> que;

    QMutex mx;
};
  

wakeOne используется в потоках как своего рода функция «публикации»» и wait вызывается из других потоков и ожидает неопределенно долго, пока сообщение не появится в очереди. В некоторых случаях роли между потоками меняются местами на разных этапах и с использованием отдельных очередей.

Является ли это вообще законным способом использования QMutex путем создания локальной переменной? Я вроде понимаю, почему кто-то мог это сделать, чтобы избежать взаимоблокировки при чтении размера que , но как это вообще работает? Есть ли более простой и идиоматичный способ добиться такого поведения?

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

1. QMutex Не является локальной — QMutexLocker являются s, и это совсем другое дело.

2. @PaulSanders wx ЯВЛЯЕТСЯ локальным? Это тоже заставило меня споткнуться, я думал, что они блокируют один и тот же мьютекс, прежде чем я присмотрелся поближе. И, судя по истории, первая ревизия в репозитории была такой, но с mx , тогда это было «исправлено», потому что это вызывало взаимоблокировки

3. Ах да, извините, пропустил это. Это действительно выглядит странно (и сломано).

4. Я видел подобный устаревший код: человек, написавший этот код, не знает, как работает мьютекс.

5. @DmitryKuzminov смотрит на название Надеюсь, мы говорим не об одном проекте

Ответ №1:

Наличие локальной переменной условия законно. Но обычно это не имеет смысла.

Как вы уже поняли, в данном случае это неправильно. Вы должны использовать член:

 void wait(DataTypeamp; msg)
{
    QMutexLocker cvlock_(amp;mx);
    while (que.empty()) 
        QWaitCondition::wait(amp;mx);

    msg = que.front();            
    que.pop();
}
  

Обратите также внимание, что вы должны иметь while вместо if вокруг вызова QWaitCondition::wait . Это связано со сложными причинами (возможного) ложного пробуждения — документы Qt здесь неясны. Но что более важно, тот факт, что пробуждение и последующее повторное получение мьютекса не является атомарной операцией, означает, что вы должны перепроверить очередь переменных на предмет пустоты. Это мог быть последний случай, когда вы ранее получали взаимоблокировки / UB.

Рассмотрим сценарий пустой очереди и вызывающего объекта (поток 1) для wait ввода QWaitCondition::wait . Этот поток блокируется. Затем появляется поток 2, который добавляет элемент в очередь и вызывает wakeOne . Поток 1 пробуждается и пытается повторно запросить мьютекс. Однако поток 3 появляется в вашей реализации wait, принимает мьютекс перед потоком 1, видит, что очередь не пуста, обрабатывает единственный элемент и движется дальше, освобождая мьютекс. Затем поток 1, который был разбужен, наконец, получает мьютекс, возвращается из QWaitCondition::wait и пытается обработать … пустую очередь. Ого.

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

1. да, что приводит к UB, потому что они получали указатель на .. что-то.. как _Msg или постоянно заканчивающийся дополнительным элементом, оставленным в очереди (до «исправления»). Также похоже, что это должно очистить очередь в сценарии, в котором они ее использовали, потому что после некоторых проверок я выяснил, что они никогда не использовали ту же очередь для «обратной связи», насколько я могу судить