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

265
14 января 2018, 05:25

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

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

[UPDATE]

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

#include <iostream>
#include <thread>
#include <mutex>
std::mutex m_console;
void Log(){
    for( int i = 0; i < 127; i++){
        std::lock_guard<std::mutex> lock(m_console);
        std::cout << i << std::endl;
    }
}
void Lag(){
    for( char i = 0; i < 127; i++){
        std::lock_guard<std::mutex> 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<std::mutex> lock(m_console);
        std::cout << "azaza" << std::endl;
    }
    system("pause");
    return 0;
}

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

Answer 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 числах просто ничего не увидите - первый поток просвистит, пока второй будет создаваться... Или сделайте их побольше, или добавьте, например, задержку небольшую...

Answer 2

std::thread::join нужен в двух случаях:

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

  2. Вы единоразово распараллелили вычисления и теперь вам надо дождаться результатов, чтобы собрать их в кучу и продолжить выполнение.

Answer 3

thread.join приостанавливает выполнение текущего потока до тех пор, пока поток, для которого было вызвано join() не завершится.

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

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

В заключение: существует поверье, что чем меньше мютексов, тем лучше, они сильно захламляют код и делают его трудно понимаемым. Поэтому не сильно ими увлекайтесь.

Answer 4

В примере вашего кода все выполняется с точностью до наоборот. Вы запускаете новый поток td::thread(Log), который, как и ожидалось, будет выполнять функцию Log. Но при этом вы сразу вызываете join() который приостанавливает текущий поток до тех пор, пока поток, для которого он вызван не окончит свое выполнение. Вы делаете это два раза. И два раза вы останавливаете главный поток программы. В результате будут созданы два потока, но второй создаться и выполнится только после того, как окончится первый, поскольку для него вызван join()

Попробуйте так:

#include <iostream>
#include <thread>
#include <mutex>
std::mutex m_console;
void Log(){
    for( int i = 0; i < 127; i++){
        std::lock_guard<std::mutex> lock(m_console);
        std::cout << i << std::endl;
    }
}
void Lag(){
    for( char i = 0; i < 127; i++){
        std::lock_guard<std::mutex> 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<std::mutex> lock(m_console);
        std::cout << "azaza" << std::endl;
    }
    // but call there
    one.join();
    two.join();
    system("pause");
    return 0;
}
READ ALSO
C# инжектор не инжектит dll на c++

C# инжектор не инжектит dll на c++

Есть инжектор на c#, он написан с помощью импортированных из c++ функций и EAT hookВообще нет идей почему это не работает

234
GoogleMock: ошибка MTd_StaticDebug при сборке проекта

GoogleMock: ошибка MTd_StaticDebug при сборке проекта

Собрал библиотеки из фреймворка для тестирования GoogleMock gmocklib, подключил к проекту, но при сборке получаю ошибку:

272
Перевод дробного числа в IEEE754

Перевод дробного числа в IEEE754

В стандарте IEEE754 дробные числа представляются следующим образом:

259
Как запустить программу в другой папке? - C++

Как запустить программу в другой папке? - C++

Существует у меня функция, которая создает логКак бы я не крутился - файл создается в папке, где запустили программу

220