Правильность использования std :: atomic & lt; bool & gt; в сочетании с std :: mutex

Я написал класс очереди с общим приоритетом.
Для подачи сигнала о прекращении подачи данных я использую метод 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;
}

0

Решение

Для обеспечения правильности вы должны изменить данные, относящиеся к «условию», удерживающему блокировку, condition_variable приобретает в .wait(...),

Хотя это и не нормативно, самое ясное утверждение, которое я могу найти, это:

Даже если общая переменная является атомарной, ее необходимо изменить в соответствии с
мьютекс, чтобы правильно опубликовать модификацию в ожидании
нить.

Вот: http://en.cppreference.com/w/cpp/thread/condition_variable

Это совершенно точно точно отвечает на ваш вопрос!

Cancel() нужно держать mtx, В этот момент атомарность перестает помогать.

Я не уверен, где находится нормативное заявление, но я знаю, что это относится к сообществу MSVC ++.

Вам не нужно держать замок, когда вы notify_one() (или же notify_all()) но вы должны удерживать его, когда изменяете «общее состояние» (в данном случае очередь или флаг).

1

Другие решения

Нормальным / наиболее частым случаем, вероятно, является то, что очередь готова (не выполнена), тогда как состояние «Готово», вероятно, используется только во время завершения. Может быть мало смысла оптимизировать для готового случая с использованием атомарного.

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

0

По вопросам рекламы [email protected]