Страницы

Поиск по вопросам

понедельник, 6 января 2020 г.

Многопоточность - в чём смысл std::thread.join()?

#cpp #многопоточность


Сколько использую потоки, никогда их не join()-ил... В моём понимании, для реализации
многопоточности thread-ы нужно "отпускать в вольное плавание" при помощи detach(),
и регулировать мьютексами доступ к общим участкам памяти.

Объясните пожалуйста где я заблуждаюсь?... Ведь везде рекомендуют использовать join(),
но я не понимаю какой смысл вызвать в функции поток с join() и остановить тем самым
её выполнение пока поток не отработает... Где же тогда многопоточность?

[UPDATE]

Прочитав ответы, я так и не понял где многопоточность, например, в этом коде?

#include 
#include 
#include 
std::mutex m_console;
void Log(){
    for( int i = 0; i < 127; i++){
        std::lock_guard lock(m_console);
        std::cout << i << std::endl;
    }
}
void Lag(){
    for( char i = 0; i < 127; i++){
        std::lock_guard lock(m_console);
        std::cout << i << std::endl;
    }
}
int main(int argc, char *argv[]){
    std::thread(Log).join();
    std::thread(Lag).join();
    for( int i = 0; i < 127; i++){
        std::lock_guard lock(m_console);
        std::cout << "azaza" << std::endl;
    }
    system("pause");
    return 0;
}


Если всё выполнится поочерёдно, где тогда распараллеливание, ради которого потоки
и существуют? Даже если локи убрать, информация в консоли не перемешается, как ожидается...
    


Ответы

Ответ 1



Зависит от того, что именно вам нужно. Например, вы распараллелили какие-то вычисления - и что теперь вам делать? Например, ваши потоки каждый считает какое-то значение, которые потом нужно просуммировать. Как вы это сделаете, отправив их в detach? будете постоянно опрашивать какие-то флаги, которые эти потоки должны выставить? :) Ваши потоки нужно было вызывать примерно так: std::thread Lo(Log); std::thread La(Lag); for( int i = 0; i < 127; i++){ std::cout << "azaza" << std::endl; } Lo.join(); La.join(); Только учтите, что мьютексы там не нужны, cout и так достаточно умный, и что вы при этих 127 числах просто ничего не увидите - первый поток просвистит, пока второй будет создаваться... Или сделайте их побольше, или добавьте, например, задержку небольшую...

Ответ 2



std::thread::join нужен в двух случаях: Ваша программа завершается, вы вот-тот выйдите из main(), и надо корректно завершить работу потоков (уничтожить объекты, закрыть дескрипторы, опустошить кеши и т. д.). Тогда вы сначала как-то уведомляете их о необходимости завершения, а затем вызываете для каждого из них join(). Без этого ожидания стандартная библиотека прибьёт процесс сразу же по выходу из main(). Вы единоразово распараллелили вычисления и теперь вам надо дождаться результатов, чтобы собрать их в кучу и продолжить выполнение.

Ответ 3



thread.join приостанавливает выполнение текущего потока до тех пор, пока поток, для которого было вызвано join() не завершится. Обычно потоки применяются для выполнение неблокирующей ресурсо-, время-затратной операции.Оказывается, что намного удобнее не отпускать потоки в свободное плавание, а контролировать их из одного главного потока, приостанавливая его, когда он ожидает значения из дочерних потоков (этот подход рекомендуют большинство источников). Более того, если мы отпустим поток в свободное плавание через thread.detach() мы не сможем его контролировать и получим кучу сообщений об ошибках и утечку памяти если таких потоков будет много. Поток, для которого был вызван detach не можем быть использован в контексте функции join(). Специфика потоков такова, что они знают, какой поток является родительским для них и только родительский поток в Си и плюсах может вызвать join() и только для своих дочерних потоков. В заключение: существует поверье, что чем меньше мютексов, тем лучше, они сильно захламляют код и делают его трудно понимаемым. Поэтому не сильно ими увлекайтесь.

Ответ 4



В примере вашего кода все выполняется с точностью до наоборот. Вы запускаете новый поток td::thread(Log), который, как и ожидалось, будет выполнять функцию Log. Но при этом вы сразу вызываете join() который приостанавливает текущий поток до тех пор, пока поток, для которого он вызван не окончит свое выполнение. Вы делаете это два раза. И два раза вы останавливаете главный поток программы. В результате будут созданы два потока, но второй создаться и выполнится только после того, как окончится первый, поскольку для него вызван join() Попробуйте так: #include #include #include std::mutex m_console; void Log(){ for( int i = 0; i < 127; i++){ std::lock_guard lock(m_console); std::cout << i << std::endl; } } void Lag(){ for( char i = 0; i < 127; i++){ std::lock_guard lock(m_console); std::cout << i << std::endl; } } int main(int argc, char *argv[]){ // don't call join std::thread one(Log); std::thread two(Lag); for( int i = 0; i < 127; i++){ std::lock_guard lock(m_console); std::cout << "azaza" << std::endl; } // but call there one.join(); two.join(); system("pause"); return 0; }

Комментариев нет:

Отправить комментарий