Коли я повинен використовувати 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
}


І потоки, 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 (), тоді вихід () не буде викликаний з main (), і, отже, програма продовжуватиме виконання до тих пір, поки всі окремі потоки не завершаться. Тоді виклик exit () буде називатися.
Саутертон

1
@Matthieu, чому ми не можемо приєднатись до деструктора std :: thread?
Джон Сміт

2
@johnsmith: Відмінне запитання! Що відбувається, коли ви приєднаєтесь? Ви чекаєте, поки нитка завершиться. Якщо викинутий виняток, деструктори виконуються ... і раптом розповсюдження вашого винятку припиняється, поки нитка не припиняється. Є багато причин цього не робити, особливо якщо він чекає на вхід з поточно припиненої нитки! Тож дизайнери вирішили зробити це явним вибором, а не вибрати спірний дефолт.
Матьє М.

@Matthieu Я думаю, ви маєте на увазі виклик join (), перш ніж буде досягнуто деструктора std :: thread. Ви можете (і повинні?) Приєднатись () до деструктора класу, що вкладається?
Хосе Кінтеріро

4
@JoseQuinteiro: Насправді, на відміну від інших ресурсів, рекомендується не приєднуватися до деструктора. Проблема полягає в тому, що з'єднання не закінчується потоком, воно просто чекає, коли воно закінчиться, і на відміну від вас є сигнал, який може призвести до припинення потоку, ви можете довго чекати ... блокуючи поточну нитку, стек якої розкручується і не дозволяє цьому поточному потоку не закінчуватися, тим самим блокуючи його, що чекає тощо. Отже, якщо ви не впевнені, що можете зупинити задану нитку за розумну кількість часу, краще не чекати це в деструкторі.
Матьє М.

25

Вам слід зателефонувати, detachякщо ви не збираєтесь чекати завершення потокуjoin але натомість він просто продовжуватиме працювати, доки він не завершиться, а потім завершиться, не вимагаючи, щоб головний потік конкретно його чекав.

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

Це фатальна помилка, якщо об’єкт потоку закінчує своє життя і ні, joinні detachйого не викликали; у цьому випадку terminateвикликається.


12
Ви повинні згадати, що термінал викликається в деструкторі, якщо нитка не була ні з'єднана, ні від'єднана .
нісід

11

Якщо ви від'єднуєте нитку, це означає, що вам не доведеться join()її перед виходом main().

Бібліотека ниток насправді буде чекати кожної такої нитки нижче основної , але вам не варто її хвилювати.

detach()в основному корисний, коли у вас є завдання, яке потрібно виконати у фоновому режимі, але ви не дбаєте про його виконання. Зазвичай це стосується деяких бібліотек. Вони можуть мовчки створити фоновий робочий потік і від'єднати його, щоб ви навіть не помітили його.


Це не дає відповіді на запитання. У відповіді, в основному, зазначено, що "ви відхиляєтесь, коли відхиляєтесь"
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;
}

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 закінчився, з новою ниткою нічого не трапиться.


Гаразд, я повертаю те, що я сказав лише зараз. @ TobySpeight
DinoStray

1
Зауважте, що якщо ви використовуєте &у захопленні лямбда, ви використовуєте змінні області, що додаються, за посиланням, тож краще бути впевненим, що термін служби будь-якої посилання на вас довший, ніж термін служби потоку.
DavidJ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.