Как отменить функцию std::async?

Возможный дубликат:
Есть ли способ отменить/отсоединить будущее в C++11?

Существует функция-член, которая выполняется асинхронно с использованием std::future и std::async. В некоторых случаях мне нужно его отменить. (Функция последовательно загружает близлежащие объекты, а иногда объекты выходят за пределы диапазона при загрузке.) Я уже прочитал ответы на этот вопрос касается той же проблемы, но я не могу заставить его работать.

Это упрощенный код с той же структурой, что и моя реальная программа. Вызов Start() и Kill() во время работы асинхронного режима вызывает сбой из-за нарушения прав доступа для input.

На мой взгляд код должен работать следующим образом. Когда вызывается Kill(), флаг выполнения отключается. Следующая команда get() должна дождаться завершения потока, что она и делает вскоре, поскольку проверяет флаг выполнения. После отмены потока указатель input удаляется.

#include <vector>
#include <future>
using namespace std;

class Class
{
    future<void> task;
    bool running;
    int *input;
    vector<int> output;

    void Function()
    {
        for(int i = 0; i < *input; ++i)
        {
            if(!running) return;
            output.push_back(i);
        }
    }

    void Start()
    {
        input = new int(42534);
        running = true;
        task = async(launch::async, &Class::Function, this);
    }

    void Kill()
    {
        running = false;
        task.get();
        delete input;
    }
};

Кажется, что поток не замечает переключения флага выполнения на false. В чем моя ошибка?


person danijar    schedule 13.01.2013    source источник
comment
Может попробовать std::atomic<bool> running;?   -  person aschepler    schedule 13.01.2013
comment
Это не компилируется, Function.get()?   -  person ronag    schedule 13.01.2013
comment
Почему input является указателем на память, выделенную кучей?   -  person Konrad Rudolph    schedule 13.01.2013
comment
во-первых, running не синхронизируется. попробуйте объявить его как atomic<bool>. вы также можете попробовать использовать volatile и синхронизировать вручную, если хотите, скорее всего, два потока работают на разных ядрах, а компилятор хранит running в реестре.   -  person Andy Prowl    schedule 13.01.2013
comment
Вы должны разыменовать input в цикле for: for(int i = 0; i < *input; ++i)   -  person Olaf Dietsche    schedule 13.01.2013
comment
@ЭндиПроул. Спасибо, поскольку я новичок в многопоточности, я никогда не слышал о них, поэтому я проведу небольшое исследование. Я исправил опечатки, на которые ссылались Ронаг и Олаф Дитше.   -  person danijar    schedule 13.01.2013
comment
@КонрадРудольф. Потому что это упрощенный пример кода. Моя программа чтения использует в качестве входных данных огромный трехмерный массив.   -  person danijar    schedule 13.01.2013
comment
@AndyProwl, volatile тут не поможет, к тредам это не имеет никакого отношения.   -  person Jonathan Wakely    schedule 13.01.2013
comment
@Джонатан Уэйкли. Извините, я понятия не имел, что означает атом, когда читал другой вопрос.   -  person danijar    schedule 13.01.2013
comment
@JonathanWakely: не могли бы вы немного объяснить, почему это не имеет ничего общего с потоками? volatile заставляет компилятор не сохранять эту переменную в реестре, чтобы изменения, сделанные потоком, работающим на одном ядре, были видны потокам, работающим на других ядрах. конечно, это не означает, что нет гонки данных, поэтому я указал, что следует использовать atomic<bool>   -  person Andy Prowl    schedule 13.01.2013
comment
@ЭндиПроул. Хотел бы я принять ваш первый комментарий в качестве ответа, все в порядке, используя atomic_bool или atomic<bool>. Я бы вам написал ответ, я приму это.   -  person danijar    schedule 13.01.2013
comment
поделиться этим: в этом нет необходимости. также потому, что @aschepler опередил меня на несколько секунд, пока я писал свой комментарий, поэтому он тоже мог получить признание.   -  person Andy Prowl    schedule 13.01.2013
comment
Понятно, спасибо и @aschepler! Я так счастлив, потому что я трачу последние несколько часов на эту часть работы с потоками.   -  person danijar    schedule 13.01.2013
comment
@AndyProwl volatile только не позволяет компилятору оптимизировать доступ к памяти для переменной, но не мешает ЦП переупорядочивать доступ к памяти, поэтому изменения в памяти, сделанные одним потоком, по-видимому, произошли в другом порядке для другого потока. Это причина существования атомарных и блокировок, которые используют соответствующие барьеры памяти, чтобы гарантировать, что изменения видны другим потокам в правильном порядке. При использовании атомов или блокировок volatile не требуется.   -  person Maxim Egorushkin    schedule 13.01.2013
comment
@AndyProwl, НЕТ НЕТ НЕТ! volatile влияет только на один поток, предотвращая переупорядочение кода компилятором, он ничего не делает для предотвращения аппаратного переупорядочения и ничего не делает для синхронизации памяти между потоками. volatile С++ не похож на volatile Java. См. hpl.hp.com/personal/ Hans_Boehm/c++mm/old_snapshots/ и drdobbs.com/ parallel/volatile-vs-volatile/212701484 и запишите его 100 раз: volatile бесполезно для безопасности потоков   -  person Jonathan Wakely    schedule 13.01.2013
comment
@MaximYegorushkin спасибо, Макс, ты меня опередил   -  person Jonathan Wakely    schedule 13.01.2013
comment
@JonathanWakely это должно быть отсутствие надлежащей синхронизации)   -  person Maxim Egorushkin    schedule 13.01.2013
comment
Чтобы быть немного более явным, если использование atomic<T>, а затем volatile не дает никаких преимуществ, код уже защищен от гонок и проблем с видимостью. Если не использовать atomic<T> (или нестандартные эквиваленты), то volatile недостаточно для корректности, поэтому вам следует использовать atomic<T>. volatile предназначен для доступа к оборудованию, а не для многопоточности.   -  person Jonathan Wakely    schedule 13.01.2013
comment
@JonathanWakely: во-первых, не нужно кричать. во-вторых, я никогда не говорил, что volatile в C++ помогает предотвратить гонки данных. в-третьих, я никогда не упоминал volatile в Java, что на самом деле является чем-то совершенно другим (кстати, я не программист на Java). я никогда не упоминал, что volatile заменяет синхронизацию любым возможным способом.   -  person Andy Prowl    schedule 13.01.2013
comment
@MaximYegorushkin: как я уже писал, я не утверждаю, что использование volatile само по себе предотвращает гонки данных. volatile просто заставляет компилятор не сохранять эту переменную в регистре. конечно, atomic<bool> — это первый вариант, поэтому я написал его первым, кстати   -  person Andy Prowl    schedule 13.01.2013
comment
Так что даже не упоминайте volatile в этом контексте, это бесполезно и не имеет отношения к вопросу. Все, что он делает, — это распространяет неверное мнение о том, что volatile каким-либо образом относится к многопоточности.   -  person Jonathan Wakely    schedule 13.01.2013
comment
@JonathanWakely: я не думаю, что volatile не имеет отношения к многопоточности, которая является темой этого вопроса. например, прочитайте это: drdobbs.com/cpp/volatile- многопоточные программисты-b/. конечно, atomic<bool> действительно решает всю проблему, но упоминание о похожих темах не кажется мне преступлением   -  person Andy Prowl    schedule 13.01.2013
comment
@AndyProwl: идея Александреску совершенно другая. Он использует volatile как тег именно потому, что в противном случае он бесполезен и, следовательно, не используется; но это всего лишь тег, если бы он мог использовать вместо этого тег helloworld, он мог бы это сделать.   -  person Matthieu M.    schedule 13.01.2013
comment
@MatthieuM.: Извините, но это не то, о чем говорится в разделе «Назад к примитивным типам». Я имею в виду, что в нем четко указано, что объявление переменной как volatile недостаточно для предотвращения гонки данных (для этого вам нужны мьютексы), но также указано, что это должно быть сделано ( Если инкремент и декремент должны вызываться из разных потоков, приведенный выше фрагмент содержит ошибку. Во-первых, ctr_ должен быть изменчивым). Теперь, если Александреску ошибается, как говорит Джонатан Уэйкли, то мне очень жаль, я всегда находил его надежным источником. Я знаю, что C++11 имеет лучший примитив. Я не согласен, volatile не имеет отношения. Останавливаться.   -  person Andy Prowl    schedule 13.01.2013
comment
@MatthieuM. Подробнее о изменчивом теге для потокобезопасных функций-членов: Херб Саттер постулирует, что в C++11 const == thread safe см. channel9.msdn.com/posts/   -  person Maxim Egorushkin    schedule 13.01.2013
comment
@MaximYegorushkin: Херб Саттер крайне неточен в этом утверждении. Во-первых, на самом деле он имеет в виду const => потокобезопасность, а не == (он обновил свои слайды). Во-вторых, даже это является чрезмерным упрощением: const просто подразумевает отсутствие гонок данных с другими операциями чтения, где гонка данных определена в параграфе 1.10. Вот что на самом деле хочет сказать Саттер.   -  person Andy Prowl    schedule 13.01.2013


Ответы (1)


Поскольку никто на самом деле не ответил на вопрос, я сделаю это.

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

Один из возможных способов, который может произойти, заключается в том, что компилятор анализирует код Function, определяет, что в этом потоке никогда не выполняется запись в переменную, и, поскольку это не атомарный объект, записи других потоков не должны быть видны, поэтому он вполне законно изменить код на это:

void Function()
{
    if(!running) return;
    for(int i = 0; i < *input; ++i)
    {
        output.push_back(i);
    }
}

Очевидно, что в этом коде, если running изменится после запуска функции, цикл не остановится.

Стандарт C++ позволяет синхронизировать два потока двумя способами: либо использовать мьютекс и только читать или записывать переменную running, пока мьютекс заблокирован, либо сделать переменную атомарной. В вашем случае изменение running с bool на atomic<bool> обеспечит синхронизацию записи в переменную с чтением из нее, и асинхронный поток завершится.

person Jonathan Wakely    schedule 13.01.2013
comment
По всем комментариям согласен, что volatile мало. Но можете ли вы объяснить, что произойдет, если running будет объявлено volatile, но не атомарным. Я предполагаю, что такое изменение порядка выполнения перед циклом for не допускается, так что еще может пойти не так в этом примере? - person balki; 14.01.2013
comment
Компилятор не будет переупорядочивать его, но без некоторых барьеров памяти (которые volatile не подразумевает) нет гарантии, что запись в volatile переменную будет видна потоку, работающему на другом ЦП. - person Jonathan Wakely; 14.01.2013
comment
Разве не гарантируется, что она сразу же будет видна на другом процессоре, или может случиться так, что запись в переменную volatile вообще никогда не будет видна другому процессору!? В приведенном выше случае, даже если running не было видно еще пару итераций, это не вредно. - person balki; 15.01.2013
comment
Нет никакой гарантии, что он когда-либо станет видимым до конца Вселенной. Не используйте volatile для синхронизации между потоками. Используйте функции, предназначенные для синхронизации между потоками, которые дают полезные гарантии. Ваш вопрос касается С++ 11, поэтому используйте соответствующие функции С++ 11. - person Jonathan Wakely; 15.01.2013