Когда я должен использовать std :: thread :: detach?

140

Иногда я должен использовать std::threadдля ускорения моего приложения. Я также знаю, join()ждет, пока поток не завершится. Это легко понять, но в чем разница между звонком detach()и не звонком?

Я думал, что без detach(), метод потока будет работать с использованием потока независимо.

Не отрываясь:

void Someclass::Somefunction() {
    //...

    std::thread t([ ] {
        printf("thread called without detach");
    });

    //some code here
}

Звонок с отрывом:

void Someclass::Somefunction() {
    //...

    std::thread t([ ] {
        printf("thread called with detach");
    });

    t.detach();

    //some code here
}
Джинбом Хо
источник
1
возможное дублирование потоков POSIX против Detached и Joinable
n. местоимения м.
Как stdи boostнити были detachи joinмоделируется близко после POSIX нитей.
нет. местоимения м.

Ответы:

149

В деструкторе std::thread, std::terminateназывается, если:

  • нить не была присоединена (с t.join())
  • и не был отделен либо (с t.detach())

Таким образом, вы должны всегда joinили detachпоток, прежде чем потоки выполнения достигнут деструктора.


Когда программа завершает работу (т.е. mainвозвращает), остальные ожидающие потоки, выполняющиеся в фоновом режиме, не ожидаются; вместо этого их выполнение приостанавливается и их локальные объекты потока разрушаются.

Важно, что это означает, что стек этих потоков не разматывается, и поэтому некоторые деструкторы не выполняются. В зависимости от действий, которые должны были предпринять эти деструкторы, ситуация может быть такой же плохой, как если бы программа потерпела крах или была убита. Надеюсь, ОС снимет блокировки файлов и т. Д., Но вы могли повредить общую память, наполовину записанные файлы и т.п.


Итак, вы должны использовать joinили detach?

  • использование join
  • Если вам не нужно иметь больше гибкости и готовы предоставить механизм синхронизации , чтобы ждать завершения потока по своему усмотрению , в этом случае вы можете использоватьdetach
Матье М.
источник
Если бы я вызвал pthread_exit (NULL); в main (), то exit () не будет вызываться из main (), и, следовательно, программа продолжит выполнение, пока все отсоединенные потоки не завершатся. Тогда exit () будет вызван.
Саутертон
1
@Matthieu, почему мы не можем присоединиться к деструктору std :: thread?
Джон Смит
2
@johnsmith: отличный вопрос! Что происходит, когда вы присоединяетесь? Вы ждете, пока поток не завершится. Если выдается исключение, деструкторы выполняются ... и внезапно распространение вашего исключения приостанавливается до тех пор, пока поток не завершится. Для этого есть много причин, особенно если он ожидает ввода от приостановленного потока! Поэтому дизайнеры решили сделать это явным выбором, а не выбирать спорный по умолчанию.
Матье М.
@ Matthieu Я думаю, вы имеете в виду вызов join () до того, как деструктор std :: thread будет достигнут. Вы можете (и должны?) Присоединиться () к деструктору окружающего класса?
Хосе Кинтейро
4
@JoseQuinteiro: На самом деле, в отличие от других ресурсов, рекомендуется не присоединяться от деструктора. Проблема заключается в том, что присоединение вовсе не конец нити, она просто ждет , чтобы он закончится, и в отличие от вас есть сигнал на месте , чтобы вызвать поток прекратить вас может ждать в течение длительного времени ... блокирует текущий поток , чей стек выполняется и предотвращает завершение текущего потока, блокируя ожидающий его поток и т. д. Итак, если вы не уверены, что можете остановить определенный поток за разумное время, лучше не ждать это в деструкторе.
Матье М.
25

Вы должны вызывать, detachесли вы не собираетесь ждать завершения потока, joinно вместо этого поток просто продолжит работать до тех пор, пока он не будет завершен, а затем завершит работу, не ожидая, что основной поток ожидает его специально.

detachв основном высвободит ресурсы, необходимые для реализации join.

Это фатальная ошибка , если объект потока не завершает свою жизнь и ни joinни detachбыл назван; в этом случае terminateвызывается.

6502
источник
12
Следует отметить, что терминатор вызывается в деструкторе, если поток не был ни присоединен, ни отсоединен .
nosid
11

Когда вы отсоединяете нить, это означает, что вы не должны делать join()это перед выходом main().

Библиотека потоков на самом деле будет ждать каждого такого потока ниже основной , но вы не должны заботиться об этом.

detach()в основном полезно, когда у вас есть задача, которая должна быть выполнена в фоновом режиме, но вы не заботитесь о ее выполнении. Это обычно относится к некоторым библиотекам. Они могут молча создать фоновый рабочий поток и отключить его, чтобы вы даже не заметили этого.

GreenScape
источник
Это не отвечает на вопрос. Ответ в основном гласит: «Вы отсоединяетесь, когда отсоединяетесь»
rubenvb
7

Этот ответ направлен на ответ на вопрос в названии, а не на объяснение разницы между joinи detach. Итак, когда следует std::thread::detachиспользовать?

В должным образом поддерживаемом коде C ++ std::thread::detachне следует использовать вообще. Программист должен убедиться, что все созданные потоки корректно завершаются, высвобождая все полученные ресурсы и выполняя другие необходимые действия по очистке. Это подразумевает, что отказ от владения потоками путем вызова detachне является вариантом и, следовательно, joinдолжен использоваться во всех сценариях.

Однако некоторые приложения полагаются на старые и часто не очень хорошо разработанные и поддерживаемые API, которые могут содержать бесконечно блокирующие функции. Перемещение вызовов этих функций в отдельный поток, чтобы избежать блокировки других вещей, является обычной практикой. Невозможно изящно joinзавершить такой поток, поэтому его использование приведет лишь к блокировке основного потока. Это ситуация, когда использование detachбыло бы менее злой альтернативой, скажем, выделению threadобъекта с динамической длительностью хранения, а затем преднамеренной утечке.

#include <LegacyApi.hpp>
#include <thread>

auto LegacyApiThreadEntry(void)
{
    auto result{NastyBlockingFunction()};
    // do something...
}

int main()
{
    ::std::thread legacy_api_thread{&LegacyApiThreadEntry};
    // do something...
    legacy_api_thread.detach();
    return 0;
}
user7860670
источник
1

По данным cppreference.com :

Отделяет поток выполнения от объекта потока, позволяя продолжить выполнение независимо. Любые выделенные ресурсы будут освобождены после выхода из потока.

После вызова detach *thisбольше не владеет ни одной веткой.

Например:

  std::thread my_thread([&](){XXXX});
  my_thread.detach();

Обратите внимание на локальную переменную: my_threadкогда время жизни my_threadистекло, std::threadбудет вызываться деструктор и std::terminate()внутри деструктора.

Но если вы используете detach(), вы не должны использовать my_threadбольше, даже если время жизни my_threadистекло, с новым потоком ничего не произойдет.

DinoStray
источник
Хорошо, я забираю то, что я только что сказал. @ TobySpeight
DinoStray
1
Обратите внимание, что если вы используете &лямбда-захват, вы используете переменные окружения области видимости по ссылке - так что вам лучше быть уверенным, что время жизни любой ссылки, на которую вы ссылаетесь, больше, чем время жизни вашего потока.
DavidJ