Что происходит с отсоединенной нитью при выходе 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
, или с помощью семафора или флага и переменной условия), но это не гарантирует завершения потока. Действительно, если вы не поместите сигнальную часть в деструктор первого автоматического объекта потока, в общем случае будет код (деструкторы), который запускается после кода сигнализации. Если ОС расписала основной поток, чтобы потреблять результат и выйти до того, как отсоединенный поток завершит выполнение указанных деструкторов, что будет определяться?
Ответы
Ответ 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() на один и тот же целевой поток не определено.
Отсоединение потоков в основном предназначено для сохранения ресурсов, если приложению не нужно ждать окончания потока (например, демоны, которые должны работать до завершения процесса):
- Чтобы освободить дескриптор на стороне приложения: можно вывести объект
std::thread
из области видимости, не присоединяясь, что обычно приводит к вызову std::terminate()
при уничтожении. - Чтобы позволить ОС автоматически очищать ресурсы, специфичные для потока (TCB), сразу после выхода из потока, поскольку мы явно указали, что мы не заинтересованы в присоединении к потоку позже, таким образом, нельзя присоединиться к уже отсоединенному потоку.
Убийства темы
Поведение при завершении процесса такое же, как и у основного потока, который мог бы хотя бы перехватить некоторые сигналы. То, могут ли другие потоки обрабатывать сигналы, не так важно, так как можно присоединиться или завершить другие потоки в вызове обработчика сигналов основного потока. (Связанный вопрос)
Как уже говорилось, любой поток, независимо от того, отсоединен он или нет, умрет с его процессом на большинстве ОС. Сам процесс может быть остановлен путем выдачи сигнала, вызова exit()
или возврата из основной функции. Однако С++ 11 не может и не пытается определить точное поведение базовой ОС, в то время как разработчики виртуальной машины Java могут в некоторой степени абстрагировать такие различия. AFAIK, экзотические модели процессов и потоков обычно встречаются на древних платформах (на которые С++ 11, вероятно, не будет перенесен) и различных встроенных системах, которые могут иметь специальную и/или ограниченную реализацию языковой библиотеки, а также ограниченную языковую поддержку.
Поддержка потоков
Если потоки не поддерживаются, std::thread::get_id()
должен возвращать недопустимый идентификатор (сконструированный по умолчанию std::thread::id
), поскольку существует простой процесс, которому для запуска не требуется объект потока и конструктор std::thread
должен std::system_error
. Вот как я понимаю С++ 11 в сочетании с современными операционными системами. Если есть ОС с поддержкой потоков, которая не порождает основной поток в своих процессах, дайте мне знать.
Управление потоками
Если для правильного завершения необходимо сохранить контроль над потоком, это можно сделать с помощью примитивов синхронизации и/или каких-либо флагов. Однако в этом случае я предпочитаю устанавливать флаг завершения работы с последующим соединением, поскольку нет смысла увеличивать сложность путем отсоединения потоков, поскольку ресурсы все равно будут освобождены одновременно, где несколько байтов std::thread
объект против более высокой сложности и, возможно, больше синхронизирующих примитивов должно быть приемлемым.
Ответ 3
Судьба потока после выхода из программы - неопределенное поведение. Но современная операционная система очистит все потоки, созданные процессом при его закрытии.
При отсоединении std::thread
эти три условия будут продолжать выполняться:
*this
больше не владеет ни одной веткой
joinable()
всегда будет равен false
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)), чтобы ресурсы потока автоматически высвобождались при завершении потока. Общие ресурсы не будут освобождены, пока не завершатся все потоки.