Вопрос о потоке С++ — установка значения, указывающего на завершение потока

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

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

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

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


person Community    schedule 29.08.2008    source источник


Ответы (5)


Вы ни разу не упомянули тип finish_flag...

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

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

person Thomi    schedule 29.08.2008
comment
Я не буду так сильно беспокоиться о том, какое значение кеширует компилятор, как о реальных кэшах (T1, T2 и T3) процессора. Причина в том, что в многопроцессорной машине кеши для каждого процессора могут не совпадать. Это одна из причин, по которой вы используете ключевое слово volatile. - person Mark Kegel; 07.11.2008
comment
Да, может быть, но, пожалуйста: повторяйте за мной: volatile bool НЕ заменяет правильную синхронизацию. - person Thomi; 11.02.2009

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

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

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

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

person roo    schedule 29.08.2008

Я не хочу делать предположения, но похоже, что цель вашей переменной 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();
person paxos1977    schedule 21.09.2008

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

В серии Herb Sutter's Effective Concurrency очень подробно рассматривается, как мир C++ пересекается с многоядерный/многопроцессорный мир.

person Mat Noguchi    schedule 29.08.2008
comment
Позвольте мне добавить еще одно отличное чтение, которое демонстрирует, насколько сложно заключается в определении результата программы, которая не использует надлежащие примитивы синхронизации. - person Jan Wrobel; 03.07.2012

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

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

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() перед возвратом. Это гарантирует, что поток вернулся в ОС и больше не выполняется.

person bk1e    schedule 16.09.2008