Подтвердить что ты не робот

Что происходит с отсоединенной нитью при выходе main()?

Предположим, что я запускаю std::thread, а затем detach(), поэтому поток продолжает выполняться, хотя std::thread, который когда-то представлял его, выходит за рамки.

Предположим далее, что программа не имеет надежного протокола для соединения выделенного потока 1 поэтому выделенный поток все еще выполняется, когда main() завершается.

Я не могу найти ничего в стандарте (точнее, в проекте N3797 С++ 14), в котором описывается, что должно произойти, ни 1.10, ни 30.3 не содержат соответствующей формулировки.

1 Еще один, возможно, эквивалентный вопрос: "может ли отсоединенный поток когда-либо соединяться снова", потому что любой протокол, который вы изобретаете, чтобы присоединиться, сигнальная часть должна быть выполнена, поток продолжал работать, и планировщик ОС мог решить, что поток будет спать в течение часа сразу после того, как сигнализация была выполнена без возможности для получающего конца надежно обнаружить, что поток фактически завершен.

Если выполнение main() с отключенными потоками выполняется undefined, то любое использование std::thread::detach() - это поведение undefined, если основной поток никогда не выходит из 2.

Таким образом, выполнение main() с работающими отдельными потоками должно иметь определенные эффекты. Возникает вопрос: где (в стандарте С++, а не POSIX, а не OS docs,...) - это те эффекты, которые определены.

2 Отделяемая нить не может быть соединена (в смысле std::thread::join()). Вы можете дождаться результатов от отдельных потоков (например, через будущее от std::packaged_task, или с помощью семафора или флага и переменной условия), но это не гарантирует завершения потока. Действительно, если вы не поместите сигнальную часть в деструктор первого автоматического объекта потока, в общем случае будет код (деструкторы), который запускается после кода сигнализации. Если ОС расписала основной поток, чтобы потреблять результат и выйти до того, как отсоединенный поток завершит выполнение указанных деструкторов, что будет определяться?

4b9b3361

Ответ 1

Ответ на исходный вопрос "что происходит с отсоединенным потоком, когда main() выходит":

Он продолжает работать (поскольку стандарт не говорит, что он остановлен), и это четко определено, если оно не затрагивает ни (автоматические | thread_local) переменные других потоков, ни статические объекты.

Кажется, что разрешено разрешать потоковым менеджерам как статические объекты (примечание в [basic.start.term]/4 говорит так же, благодаря @dyp для указателя).

Проблемы возникают, когда уничтожение статических объектов завершено, потому что тогда выполнение переходит в режим, в котором может выполняться только код, разрешенный в обработчиках сигналов ([basic.start.term]/1, 1-е предложение). Из стандартной библиотеки С++ это только библиотека <atomic> ([support.runtime]/9, 2-е предложение). В частности, это, вообще говоря, исключает condition_variable (это реализация, определяемая, сохраняется ли это для использования в обработчике сигнала, поскольку она не является частью <atomic>).

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

Ответ на второй вопрос: "Можно ли отсоединить потоки, когда-либо соединенные снова":

Да, с семейством функций *_at_thread_exit (notify_all_at_thread_exit(), std::promise::set_value_at_thread_exit(),...).

Как отмечено в сноске [2] вопроса, сигнализировать переменную условия или семафор или атомный счетчик недостаточно для присоединения к отдельному потоку (в смысле обеспечения того, что конец его выполнения произошел раньше прием упомянутой сигнализации посредством ожидающего потока), поскольку, как правило, будет выполняться больше кода после, например, a notify_all() переменной условия, в частности деструкторы автоматических и поточно-локальных объектов.

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

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

Ответ 2

Отсоединение темы

Согласно std::thread::detach:

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

Из pthread_detach:

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

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

  1. Чтобы освободить дескриптор на стороне приложения: можно вывести объект std::thread из области видимости, не присоединяясь, что обычно приводит к вызову std::terminate() при уничтожении.
  2. Чтобы позволить ОС автоматически очищать ресурсы, специфичные для потока (TCB), сразу после выхода из потока, поскольку мы явно указали, что мы не заинтересованы в присоединении к потоку позже, таким образом, нельзя присоединиться к уже отсоединенному потоку.

Убийства темы

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

Как уже говорилось, любой поток, независимо от того, отсоединен он или нет, умрет с его процессом на большинстве ОС. Сам процесс может быть остановлен путем выдачи сигнала, вызова exit() или возврата из основной функции. Однако С++ 11 не может и не пытается определить точное поведение базовой ОС, в то время как разработчики виртуальной машины Java могут в некоторой степени абстрагировать такие различия. AFAIK, экзотические модели процессов и потоков обычно встречаются на древних платформах (на которые С++ 11, вероятно, не будет перенесен) и различных встроенных системах, которые могут иметь специальную и/или ограниченную реализацию языковой библиотеки, а также ограниченную языковую поддержку.

Поддержка потоков

Если потоки не поддерживаются, std::thread::get_id() должен возвращать недопустимый идентификатор (сконструированный по умолчанию std::thread::id), поскольку существует простой процесс, которому для запуска не требуется объект потока и конструктор std::thread должен std::system_error. Вот как я понимаю С++ 11 в сочетании с современными операционными системами. Если есть ОС с поддержкой потоков, которая не порождает основной поток в своих процессах, дайте мне знать.

Управление потоками

Если для правильного завершения необходимо сохранить контроль над потоком, это можно сделать с помощью примитивов синхронизации и/или каких-либо флагов. Однако в этом случае я предпочитаю устанавливать флаг завершения работы с последующим соединением, поскольку нет смысла увеличивать сложность путем отсоединения потоков, поскольку ресурсы все равно будут освобождены одновременно, где несколько байтов std::thread объект против более высокой сложности и, возможно, больше синхронизирующих примитивов должно быть приемлемым.

Ответ 3

Судьба потока после выхода из программы - неопределенное поведение. Но современная операционная система очистит все потоки, созданные процессом при его закрытии.

При отсоединении std::thread эти три условия будут продолжать выполняться:

  1. *this больше не владеет ни одной веткой
  2. joinable() всегда будет равен false
  3. get_id() будет равен std::thread::id()

Ответ 4

Рассмотрим следующий код:

#include <iostream>
#include <string>
#include <thread>
#include <chrono>

void thread_fn() {
  std::this_thread::sleep_for (std::chrono::seconds(1)); 
  std::cout << "Inside thread function\n";   
}

int main()
{
    std::thread t1(thread_fn);
    t1.detach();

    return 0; 
}

Запустив его в системе Linux, сообщение из thread_fn никогда не печатается. ОС действительно очищает thread_fn(), как только main() завершает работу. Замена t1.detach() на t1.join() всегда печатает сообщение, как ожидалось.

Ответ 5

Когда основной поток (то есть поток, который выполняет функцию main()) завершается, процесс завершается, и все остальные потоки останавливаются.

Ссылка: fooobar.com/questions/58867/...

Ответ 6

Чтобы другие потоки могли продолжить выполнение, основной поток должен завершиться вызовом pthread_exit(), а не exit (3). Хорошо использовать pthread_exit в main. Когда используется pthread_exit, основной поток прекращает выполнение и будет оставаться в состоянии зомби (перестал существовать) до тех пор, пока все другие потоки не завершатся. Если вы используете pthread_exit в основном потоке, не можете получить статус возврата других потоков и не можете выполнить очистку для других потоков (это можно сделать с помощью pthread_join (3)). Кроме того, лучше отсоединить потоки (pthread_detach (3)), чтобы ресурсы потока автоматически высвобождались при завершении потока. Общие ресурсы не будут освобождены, пока не завершатся все потоки.