У меня есть код, который работает так:
std::queue<int> a_queue;
bool exit = false;
void MainThreadFunc(int somedata)
{
a_queue.push(somedata);
}
void WorkerThreadFunc()
{
while (true)
{
if (exit)
return;
while (a_queue.empty());
DoSomethingWithData(a_queue.front());
a_queue.pop();
}
}
Проблема в том, что у меня действительно высокая загрузка ЦП, которая, как представляется, является результатом спин-блокировки, когда работнику нечего делать. Я пытался использовать мьютекс, но мне потребовалось бы, чтобы основной поток заблокировал его, когда в очереди ничего нет (что, конечно, не может произойти). Какие есть альтернативы, чтобы это предотвратить?
Код ниже — это то, что я узнал раньше. Это реализация очереди блокировки. Потоки могут безопасно помещать элементы в очередь, и если поток пытается извлечь элемент из очереди, когда он пуст, он будет заблокирован, пока какой-либо другой поток не поместит элементы в очередь. Надеюсь, это поможет вам
#include <queue>
#include <cassert>
#include <mutex>
#include <condition_variable>
#include <thread>
template<typename T>
class BlockingQueue
{
private:
std::mutex _mutex;
std::condition_variable _condvar;
std::queue<T> _queue;
public:
BlockingQueue(): _mutex(),_condvar(),_queue()
{
}
BlockingQueue(const BlockingQueue& rhs) = delete;
BlockingQueue& operator = (const BlockingQueue& rhs) = delete;
void Put(const T& task)
{
{
std::lock_guard<std::mutex> lock(_mutex);
_queue.push(task);
}
_condvar.notify_all();
}
T Take()
{
std::unique_lock<std::mutex> lock(_mutex);
_condvar.wait(lock,[this]{return !_queue.empty(); });
assert(!_queue.empty());
T front(std::move(_queue.front()));
_queue.pop();
return front;
}
};
С учетом заявленных требований, когда стоимость планировщика потоков / переключения контекста слишком высока, обычно лучше всего просто записать циклы, как вы делаете сейчас, чтобы удовлетворить самые жесткие требования к задержке.
Цикл вращения / занятости атомарного CAS — это, по сути, метод опроса, и, как это обычно связано с опросом, он имеет тенденцию перегружать процессор. Тем не менее, он не окупает стоимость планировщика, которого вы хотите здесь избежать, с этим сжатым 16-месячным сроком, чтобы сделать все, что вам нужно сделать, и доставить кадр. Как правило, это лучший выбор для соблюдения этого срока.
В играх, где у вас живой мир и постоянно анимированный контент, обычно не бывает длительных периодов простоя, когда ничего не происходит. В результате, обычно считается, что игра постоянно использует процессор.
Вероятно, более продуктивный вопрос, учитывая ваши требования, заключается не в том, как уменьшить загрузку ЦП, а в том, чтобы убедиться, что загрузка ЦП идет к хорошей цели. Как правило, параллельная очередь может предлагать неблокирующий, неблокирующий запрос, чтобы проверить, пуста ли очередь, что вы уже, по-видимому, задали в этой строке:
while (a_queue.empty());
Возможно, вам удастся проникнуть сюда в некоторые вещи, пока очередь пуста. Таким образом, вы не сжигаете циклы, а просто заняты, ожидая, пока очередь не станет пустой.
Опять же, обычно идеальным ответом на ваш вопрос может быть условная переменная (которая зависит от переключения контекста и пробуждения потоков). Как правило, это будет самый быстрый способ перевести поток в спящий режим (снизить нагрузку на ЦП) и запустить его в нужное время, но, учитывая жесткие требования к задержке, лучше забыть об использовании ЦП и уделить больше внимания убедившись, что это идет к хорошей цели.
std::queue
не является потокобезопасным. Как писал @Hurkyl, вам нужна блокировка, если вы хотите использовать std::queue
,
Как закодировано, если несколько потоков ждут данных в очереди, они могли бы все действовать на то же значение из a_queue.fronf()
затем позвоните a_queue.pop()
количество раз, не связанных с количеством значений, помещаемых в очередь.