С++ Тема вопроса - установка значения для указания завершения потока

Является ли следующее безопасным?

Я новичок в потоковой передаче, и я хочу делегировать процесс, требующий много времени, в отдельный поток в моей программе на С++. Используя библиотеки boost, я написал код примерно так:

thrd = new boost:: thread (boost:: bind (& myclass:: mymethod, this, & final_flag);

Где final_flag является логическим членом моего класса. Когда поток завершен, он устанавливает значение, и основной цикл моей программы проверяет изменение этого значения. Я предполагаю, что это нормально, потому что я только начинаю один поток, и этот поток - единственное, что изменяет значение (за исключением того, когда оно инициализируется до начала потока) Так это хорошо, или я что-то упускаю, и мне нужно использовать блокировки и мьютексы и т.д.

Ответ 1

Вы никогда не говорили о типе final_flag...

Если это прямой bool, тогда это может сработать, но это, конечно, плохая практика по нескольким причинам. Во-первых, некоторые компиляторы будут кэшировать чтение переменной final_flag, поскольку компилятор не всегда подбирает тот факт, что он записывается другим потоком. Вы можете обойти это, объявив bool volatile, но это приведет нас в неправильном направлении. Даже если чтения и записи происходят так, как вы ожидали, нет ничего, что могло бы помешать планировщику ОС чередовать два потока наполовину через чтение/запись. Это может быть не такая проблема здесь, когда у вас есть один read и one write op в отдельных потоках, но это хорошая идея начать, поскольку вы хотите продолжить.

Если, с другой стороны, это потокобезопасный тип, например CEvent в MFC (или equivilent in boost), тогда вы должны быть в порядке. Это лучший подход: использовать поточно-безопасные объекты синхронизации для межпоточной связи даже для простых флагов.

Ответ 2

Вместо использования переменной-члена, чтобы сигнализировать о том, что поток выполнен, почему бы не использовать condition? Вы уже используете библиотеки boost, а condition является частью библиотеки потоков.

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

В общем случае я бы предпочел, чтобы ресурс был изменен только потоком. Вы, возможно, знаете, для чего он нужен, но кто-то другой не может этого сделать, что не приводит к кончине печали, поскольку основной поток считает, что работа выполнена и пытается получить доступ к неверным данным! Это может даже удалить его, пока рабочий поток все еще использует его, а также приводит к сбою приложения. Использование condition поможет в этом.

Глядя на документацию thread, вы также можете вызвать thread.timed_join в основном потоке. timed_join будет ждать указанной суммы для потока для соединения (соединение означает, что поток финиширован)

Ответ 3

Если вы действительно хотите ознакомиться с деталями связи между потоками через разделяемую память, даже объявить переменную volatile будет недостаточно, даже если компилятор действительно использует соответствующую семантику доступа, чтобы гарантировать, что она не станет устаревшей версию данных после проверки флага. Процессор может выдавать записи и записывать как можно дольше (x86 обычно не делает, но PPC определенно делает), и в С++ 9x нет ничего, что позволяет компилятору генерировать код для упорядочения доступа к памяти соответствующим образом.

Эффективная серия Concurrency Herb Sutter имеет чрезвычайно глубокий взгляд на то, как мир С++ пересекает многоядерный/многопроцессорный мир.

Ответ 4

Я не хочу быть презумптивным, но, похоже, целью вашей переменной finished_flag является приостановка основного потока (в какой-то момент) до тех пор, пока поток thrd завершено.

Самый простой способ сделать это - использовать boost:: thread:: join

// launch the thread...
thrd = new boost::thread(boost::bind(&myclass::mymethod, this, &finished_flag);

// ... do other things maybe ... 

// wait for the thread to complete
thrd.join();

Ответ 5

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

Например, рассмотрим программу, которая загружает динамическую библиотеку (псевдокод):

lib = loadLibrary("someLibrary");
fun = getFunction("someFunction");
fun();
unloadLibrary(lib);

И пусть предположим, что эта библиотека использует ваш поток:

void someFunction() {
    volatile bool finished_flag = false;
    thrd = new boost::thread(boost::bind(&myclass::mymethod, this, &finished_flag);
    while(!finished_flag) { // ignore the polling loop, it besides the point
        sleep();
    }
    delete thrd;
}

void myclass::mymethod() {
    // do stuff
    finished_flag = true;
}

Когда myclass::mymethod() устанавливает finished_flag в true, myclass::mymethod() еще не вернётся. По крайней мере, ему все равно нужно выполнить инструкцию "return" (если не намного больше: деструкторы, управление обработчиком исключений и т.д.). Если поток, выполняющий myclass::mymethod(), получает pre-empted до этой точки, someFunction() вернется к вызывающей программе, а вызывающая программа выгрузит библиотеку. Когда поток, выполняющий myclass::mymethod(), снова запланирован для запуска, адрес, содержащий инструкцию "return", больше недействителен, и программа сработает.

Решение было бы для someFunction() вызвать thrd->join() перед возвратом. Это гарантирует, что поток вернется в ОС и больше не выполняется.