Вопрос C ++ Thread - установка значения, указывающего, что поток завершен - PullRequest
10 голосов
/ 29 августа 2008

Безопасно ли следующее?

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

thrd = новый boost :: thread (boost :: bind (& myclass :: mymethod, this, & done_flag);

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

Ответы [ 5 ]

11 голосов
/ 29 августа 2008

Вы никогда не упоминали тип готового_флага ...

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

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

7 голосов
/ 29 августа 2008

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

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

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

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

5 голосов
/ 21 сентября 2008

Я не хочу быть предположительным, но похоже, что цель вашей переменной done_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 голосов
/ 30 августа 2008

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

Эффективный параллелизм Херба Саттера * В серии 1004 * очень подробно рассматривается, как мир C ++ пересекается с многоядерным / многопроцессорным миром.

2 голосов
/ 16 сентября 2008

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

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

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's besides the point
        sleep();
    }
    delete thrd;
}

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...