Одним из простых способов было бы добавить флаг, который вы установили извне, когда хотите прервать операцию pop()
, которая уже заблокирована. И тогда вам нужно будет решить, что прерванный pop()
вернет. Один из способов - создать исключение, другой - вернуть std::optional<T>
. Вот первый метод (я напишу только измененные части.)
Добавьте этот тип там, где вы считаете нужным:
struct AbortedPopException {};
Добавьте это в поля своего класса:
mutable std::atomic<bool> abort_flag = false;
Также добавьте этот метод:
void abort () const {
abort_flag = true;
}
Измените while
l oop в методе pop()
следующим образом: (вам вообще не нужен while
, так как я считаю, что условная переменная wait()
метод, который принимает лямбду, делает не пробуждение / возврат внезапно; т. е. l oop уже находится в ожидании.)
not_empty.wait(lock, [this]{return !queue.empty() || abort_flag;});
if (abort_flag)
throw AbortedPopException{};
Вот и все (я верю).
В вашем main()
, когда вы хотите отключить «потребителя», вы можете позвонить abort()
в свою очередь. Но вам придется справиться и с брошенным исключением. В основном это ваш сигнал «выхода».
Некоторые примечания:
Не отсоединяйте от потоков! Особенно здесь, где AFAICT нет никаких причин для этого (и некоторой реальной опасности тоже.) Просто подайте им сигнал о выходе (любым подходящим способом) и join()
их.
Ваш stop
флаг должен быть атомом c. Вы читаете из него в фоновом потоке и пишете в него из основного потока, и они могут (и фактически могут) перекрываться во времени, поэтому ... гонка данных!
I не понимаю, почему в вашей очереди "полное" состояние и "емкость". Подумайте, нужны ли они.
ОБНОВЛЕНИЕ 1 : В ответ на комментарий ОП об отключении ... Вот что происходит в вашей основной ветке:
- Вы порождаете поток «продюсера» (то есть тот, который помещал вещи в очередь)
- Затем вы делаете всю работу, которую хотите сделать (например, потребляете вещи в очереди)
- Иногда, возможно, в конце
main()
, вы даете сигнал об остановке потока (например, устанавливая флаг stop
в true
) - тогда, и только тогда вы
join()
с поток.
Это правда, что ваш основной поток будет блокироваться, пока он ожидает, пока поток поймает сигнал "стоп", выйдет из своего l oop и вернется из функции потока , но это очень очень короткое ожидание. И тебе больше нечего делать. Что еще более важно, вы будете знать, что ваш поток завершился чисто и предсказуемо, и с этого момента вы точно знаете, что этот поток не будет работать (это не важно для вас, но может иметь решающее значение для какой-либо другой задачи с потоками).
Это шаблон, которому вы обычно хотите следовать в порождающем рабочем потоке, который l oop при выполнении короткого задания.
Обновление 2 : о "полном" и «емкость» очереди. Хорошо. Это, безусловно, ваше решение. Никаких проблем с этим.
Обновление 3 : о «выбрасывании» и возврате «пустого» объекта для сигнализации об отмене «блокировки» pop()
». Я не думаю, что есть что-то плохое в метании; особенно, поскольку это очень и очень редко (это происходит один раз в конце операции производителя / потребителя). Однако, если все типы T
, которые вы хотите сохранить в вашем Queue
, имеют «недопустимый» или «пустой» "Государство, то вы, конечно, можете использовать это. Но бросание является более общим, если более "неприглядным" для некоторых людей.