В настоящее время я читаю книгу о многопоточности в C ++. В одной главе я нашел некоторый исходный код для многопоточной очереди. Это примерно так:
template<typename T>
class QueueThreadSafe
{
private:
std::mutex m_mutex;
std::queue<T> m_dataQueue;
std::condition_variable m_dataCondition;
public:
void push(T someValue)
{
std::lock_guard<std::mutex> guard(m_mutex);
m_dataQueue.push(someValue);
m_dataCondition.notify_one();
}
void pop(T &retVal)
{
std::unique_lock<std::mutex> lock(m_mutex);
m_dataCondition.wait(lock, [this]{return !m_dataQueue.empty();});
retVal = m_dataQueue.front();
m_dataQueue.pop();
}
};
Когда значение помещается в очередь, состояние данных уведомляется, и некоторые (возможные) ожидающие потоки в pop могут возобновить работу. Что меня смущает, так это ложные следы в этом сценарии. Что если в одно и то же время один поток уведомлен, другой поток одновременно просыпается? Конечно, он также видит не пустую очередь. В этом сценарии два разных потока попытались бы получить значение, где, возможно, существует только одно значение — классическое условие гонки.
Я что-то здесь упустил? Есть лучший способ сделать это?
Ложные пробуждения означают, что вам нужно проверить, что условие для пробуждения остается в силе, когда вы просыпаетесь. Так как wait
функция передана:
Поведение, когда один поток уведомляется «нормально», а другой уведомляется ложно, заключается в том, что один из них (неважно, какой из них быстрее гоняется) получает блокировку и подтверждает, что очередь не пуста, а затем выскакивает из верхнего элемента. и снимает блокировку; тот, кто проиграл гонку за блокировкой, не получает блокировку до тех пор, пока более быстрый поток не снимет блокировку, поэтому он видит уже опустошенную очередь и решает, что это было ложное пробуждение, возвращаясь ко сну.
Важно отметить, что на самом деле не имеет значения, выиграла ли заостренно пробуждаемая нить в гонке за блокировку (и предмет в очереди) или нет; один из потоков вел себя как будто проснулся нормально (он нашел условие true и работал как положено), один из них как будто проснулся (он нашел условие false и вернулся к ожиданию, как и ожидалось), и код в целом вел себя правильно ,
Я думаю, что в этой ситуации поток уведомлений и пробужденный поток имеют одинаковую возможность извлекать из очереди, это зависит только от того, как процессор примет решение по расписанию (какой из них быстрее).
Если вы не хотите указывать, какой поток должен иметь право., Тогда вы должны изменить реализацию.