Я написал класс очереди с общим приоритетом.
Для подачи сигнала о прекращении подачи данных я использую метод Cancel()
, который устанавливает знак done
в false, и приложение не может записывать / читать любые данные из очереди. Я не уверен в использовании std::atomic<bool>
в комбинации с std::mutex
а также std::condition_variable
, Я не уверен, что мое решение является поточно-ориентированным или может возникнуть состояние гонки:
Оригинальная версия Enqueue
метод это:
std::deque<T> deque;
std::mutex mtx;
std::condition_variable cv;
std::atomic<bool> done;
SharedPriorityQueue() : done(false)
{
}
~SharedPriorityQueue()
{
Cancel();
}
void Enqueue(T item)
{
if (done)
{
return;
}
std::lock_guard<std::mutex> lock(mtx);
deque.push_back(item);
cv.notify_one();
}
Тем не менее, может быть переменной done
(атомный бул) отделен от механизма блокировки мьютексом?
Для отмены очереди я использую эту конструкцию:
void Cancel()
{
if (done)
{
return;
}
done = true;
cv.notify_all();
}
Какое лучшее решение дизайнов ниже?
// A)
void Enqueue(T item)
{
if (done)
{
return;
}
{
std::lock_guard<std::mutex> lock(mtx); // lock is released before notify call
deque.push_back(item);
}
cv.notify_one();
}
// B)
void Enqueue(T item)
{
{
std::lock_guard<std::mutex> lock(mtx); // done is atomic bool and protected by the lock along with data (deque)
if (done) // atomic bool
{
return;
}
deque.push_back(item);
}
cv.notify_one();
}
// C)
void Enqueue(T item)
{
{
std::lock_guard<std::mutex> lock(mtx); // done is NOT atomic bool and is protected by the lock along with data (deque)
if (done) // simple bool
{
return;
}
deque.push_back(item);
}
cv.notify_one();
}
Обслуживающий персонал:
bool Dequeue(T& item)
{
std::unique_lock<std::mutex> lock(mtx);
while (!done && deque.empty())
{
cv.wait(lock);
}
if (!deque.empty())
{
item = deque.front();
deque.pop_front();
}
if (done)
{
return false;
}
return true;
}
Для обеспечения правильности вы должны изменить данные, относящиеся к «условию», удерживающему блокировку, condition_variable
приобретает в .wait(...)
,
Хотя это и не нормативно, самое ясное утверждение, которое я могу найти, это:
Даже если общая переменная является атомарной, ее необходимо изменить в соответствии с
мьютекс, чтобы правильно опубликовать модификацию в ожидании
нить.
Вот: http://en.cppreference.com/w/cpp/thread/condition_variable
Это совершенно точно точно отвечает на ваш вопрос!
Cancel()
нужно держать mtx
, В этот момент атомарность перестает помогать.
Я не уверен, где находится нормативное заявление, но я знаю, что это относится к сообществу MSVC ++.
Вам не нужно держать замок, когда вы notify_one()
(или же notify_all()
) но вы должны удерживать его, когда изменяете «общее состояние» (в данном случае очередь или флаг).
Нормальным / наиболее частым случаем, вероятно, является то, что очередь готова (не выполнена), тогда как состояние «Готово», вероятно, используется только во время завершения. Может быть мало смысла оптимизировать для готового случая с использованием атомарного.
Вы должны понимать, для чего вы оптимизируете, и использовать профилировщик.