Существует ли в реальной ситуации ситуация, когда простой указатель на bool как флаг отмены потока не сможет эффективно отменить поток?

Прежде всего, я понимаю, что формально, используяatomic Флаг отмены потока — очень неопределенное поведение в том смысле, что язык не определяет, будет ли эта переменная записана до выхода из потока.

На работе это было реализовано давно, и большинство потоков вычислений проверяют значение этого bool на протяжении всей своей работы, чтобы изящно отменить все, что они делают. Когда я впервые увидел это, моей первой реакцией было изменить все это, чтобы использовать его лучше (в этом случае, QThread::requestInterruption а также QThread::interruptionRequested казалось жизнеспособной альтернативой). Быстрый поиск по коду выявил около 800 вхождений этой переменной / конструкции по всей базе кода, поэтому я позволил ей уйти.

Когда я обратился к (старшему, с точки зрения многолетнего опыта) коллеге, он заверил меня, что, хотя это действительно может быть неправильно, он никогда не видел, чтобы она не выполняла свою цель. Он утверждал, что единственный случай, когда это пойдет не так, — если поток (группа) разрешено запускать, а другому потоку, который фактически изменяет этот флаг, никогда не будет разрешено выполняться, пока другие потоки не будут завершены. Он также утверждал, что в этом случае ОС будет вмешиваться и справедливо распределять время выполнения по всем потокам, что может привести к задержке отмены.

Теперь мой вопрос: есть ли в реальной ситуации (предпочтительно на обычной системе, основанной на x86 / ARM, предпочтительно C или C ++), где это действительно не удается?

Заметьте, я не пытаюсь выиграть спор, так как мой коллега согласен, что это технически неверно, но я хотел бы знать, может ли это вызвать проблемы и при каких обстоятельствах это может произойти.

3

Решение

Самый простой способ справиться с этим — свести его к довольно тривиальному примеру. Компилятор оптимизирует чтение флага, потому что он не является атомарным и записывается другим потоком как UB; поэтому флаг никогда не получится на самом деле читать.

Аргумент вашего коллеги основан на предположении, что компилятор на самом деле нагрузка флаг, когда вы отменяете ссылку на флаг. Но на самом деле он не обязан это делать.

#include <thread>
#include <iostream>

bool cancelled = false;
bool finished = false;

void thread1() {
while(!cancelled) {
std::cout << "Not cancelled";
}
}
int main() {
std::thread t(thread1);
t.detach();
cancelled = true;
while(!finished) {}
}

Чтобы запустить на колиру, загрузить http://coliru.stacked-crooked.com/a/5be139ee34bf0a80, вам нужно будет отредактировать и внести тривиальное изменение, потому что кэширование прерывается для фрагментов, которые не заканчиваются.

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

1

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

Пока вы ждете окончания потоков перед использованием их данных, на практике все будет в порядке: барьеры памяти, установленные std::thread::join или же QThread::wait защитит тебя.

Ваше беспокойство не о cancelled переменная, пока она нестабильна, на практике все в порядке. Вы должны беспокоиться о чтении несогласованного состояния данных, измененных потоками.

0

Как можно понять из комментария Майна, пример кода Puppy не демонстрирует проблему. Несколько небольших изменений необходимы.

Во-первых, мы должны добавить finished = true; в конце thread1 так что программа даже делает вид, что может быть завершена.

Теперь оптимизатор не может проверить каждую функцию в каждой единице перевода, чтобы убедиться, что cancelled на самом деле всегда false при входе thread1, поэтому он не может сделать смелую оптимизацию, чтобы удалить цикл while и все, что после него. Мы можем исправить это, установив cancelled ложно в начале thread1,

С учетом предыдущего добавления, для справедливости, мы также должны постоянно устанавливать cancelled чтобы правда в mainпотому что в противном случае мы не можем гарантировать, что одно назначение в main не запланировано после первоначального назначения в thread1,

Редактировать: добавлены классификаторы и синхронное объединение вместо отделения.

#include <thread>
#include <iostream>

bool cancelled = false;
bool finished = false;

void thread1() {
cancelled = false;
while(!cancelled)
;
finished = true;
}
int main() {
std::thread t(thread1);
while(!finished) {
std::cout << "trying to cancel\n";
cancelled = true;
}
t.join();
}
0
По вопросам рекламы [email protected]