Короткий ответ: ваш код может быть правильным или неправильным; Вы не показали, как именно манипулируют full
.
Отдельные биты кода C ++ никогда не являются поточно-ориентированными. Потокобезопасность является реляционным свойством кода; два бита кода могут быть потокобезопасными по отношению друг к другу, если они никогда не могут вызвать состояние гонки.
Но один бит кода не является поточно-ориентированным; сказать что-то потокобезопасно - это все равно, что сказать что-то "такой же высоты".
Шаблон переменной условия "monkey see monkey do" таков:
template<class T>
class cv_bundle {
std::mutex m;
T payload;
std::condition_variable cv;
public:
explicit cv_bundle( T in ):payload(std::move(in)) {}
template<class Test, class Extract>
auto wait( Test&& test, Extract&& extract ) {
std::unique_lock<std::mutex> l(m);
cv.wait( l, [&]{ return test(payload); } );
return extract(payload);
}
template<class Setter>
void load( Setter&& setter, bool only_one = true ) {
bool is_set = false;
{
std::unique_lock<std::mutex> l(m);
is_set = setter( payload );
}
if (!is_set) return; // nothing to notify
if (only_one)
cv.notify_one();
else
cv.notify_all();
}
};
test
принимает T& payload
и возвращает истину, если есть что потреблять (т. Е. Пробуждение не является ложным).
extract
принимает T& payload
и возвращает любую информацию, которую вы хотите от него. Обычно он сбрасывает полезную нагрузку.
setter
изменяет T& payload
таким образом, что test
возвращает true
. Если это так, он возвращает true
. Если он решит не делать, он вернет false
.
Все 3 вызываются в мьютекс блокирующем доступ к T payload
.
Теперь вы можете создавать вариации этого, но сделать это очень сложно. Например, не думайте, что атомарная полезная нагрузка означает, что вам не нужно блокировать мьютекс.
Хотя я соединил эти 3 вещи вместе, вы можете использовать один мьютекс для набора переменных условий или использовать мьютекс для большего, чем просто переменную условия. Полезной нагрузкой может быть логическое значение, счетчик, вектор данных или нечто более чуждое; в общем, он всегда должен быть защищен мьютексом. Если оно атомарное, в какой-то момент в открытом интервале между изменяемым значением и уведомлением мьютекс должен быть заблокирован, иначе вы рискуете потерять уведомление.
Ручное управление циклами вместо передачи в лямбду является модификацией, но описание того, какие типы ручных циклов являются законными, а какие являются условиями гонки, является сложной проблемой.
В сущности, я избегаю оставлять этот стиль использования условных переменных типа "культ груза" обезьянами, если у меня нет чрезвычайно веских причин. И затем я вынужден прочитать модель памяти и многопоточности C ++, которая не делает мой день, и это означает, что мой код, скорее всего, не будет правильным.
Обратите внимание, что если любая из лямбд прошла в go и перезвонила в cv_bundle
, код, который я показал, больше не действителен.