当main()退出时,一个分离的线程会怎样?


152

假设我先启动一个std::thread,然后启动detach()它,那么即使std::thread那个曾经代表它,但超出范围,线程仍继续执行。

进一步假定该程序不具有用于加入分离线程1的可靠协议,因此分离线程在main()退出时仍然运行。

我无法在标准(更确切地说,在N3797 C ++ 14草案中)中找到任何内容,该标准描述了应该发生的情况,无论1.10还是30.3都没有相关的措辞。

1另一个可能等效的问题是:“是否可以再次连接一个分离的线程”,因为无论您想加入哪种协议,都必须在线程仍在运行时完成信令部分,并且OS调度程序可能决定在执行信令后立即使线程休眠一个小时,而接收端无法可靠地检测到线程实际上已完成。

如果main()用分离的线程运行时耗尽是未定义的行为,则除非主线程永不退出2,否则对的任何使用std::thread::detach()都是未定义的行为。

因此,main()用分离的线程运行时用尽必须具有定义的效果。问题是:在哪里定义了这些效果(在C ++标准中,不是POSIX,不是OS docs,...)。

2分离的线程无法连接(在的意义上std::thread::join())。您可以等待来自分离线程的结果(例如,通过来自的Future std::packaged_task或通过计数信号量或标志和条件变量),但这不能保证线程已完成执行。事实上,除非你把信令部分进入线程的第一个自动对象的析构函数,也在一般情况下,是代码(析构函数),其运行的信号代码。如果操作系统安排主线程使用结果并在分离的线程完成运行所述析构函数之前退出,那么将定义发生什么?


5
我只能在[basic.start.term] / 4中找到一个非常模糊的非强制性说明:“在调用std::exit或退出之前终止每个线程main足以满足这些要求,但不是必须的。” (整个段落可能是相关的),另请参阅[support.start.term] / 8(返回std::exit时称为main
dyp

Answers:


45

原始问题“ main()退出时分离线程会发生什么”的答案是:

它继续运行(因为标准没有说它已停止),并且定义良好,只要它既不接触其他线程的(automatic | thread_local)变量也不接触静态对象。

似乎允许将线程管理器作为静态对象使用([basic.start.term] / 4中的注释也要多说了,多亏了@dyp的指针)。

当静态对象的销毁完成时会出现问题,因为执行会进入一种机制,在这种机制中,只有信号处理程序中允许的代码才能执行([basic.start.term] / 1,第一句)。在C ++标准库中,这只是<atomic>库([support.runtime] / 9,第二句)。特别是,通常情况下,它会排除 condition_variable(它是实现定义的,是否可以保存以用于信号处理程序,因为它不属于<atomic>)。

除非您此时未展开堆栈,否则很难避免如何避免未定义的行为。

第二个问题“可以再次连接分离的线程”的答案是:

是的,与*_at_thread_exit系列函数(notify_all_at_thread_exit()std::promise::set_value_at_thread_exit(),...)。

正如问题的脚注[2]所指出的那样,用信号发送条件变量或信号量或原子计数器不足以加入一个分离的线程(从确保在接收到该变量之前执行结束的意义上来说)(例如,通过等待线程发送信号),因为通常在notify_all()条件变量的a 之后,尤其是自动对象和线程本地对象的析构函数之后,将执行更多代码。

运行(如线程做的最后一件事的信号自动和线程本地对象的析构函数已经-发生)是什么_at_thread_exit功能的系列是专为。

因此,为了避免在没有任何超出标准要求的实现保证的情况下发生不确定的行为,您需要(手动)将分离线程与_at_thread_exit执行信号传递的功能结合在一起,或者使分离线程执行对于以下情况安全的代码:信号处理器。


17
你确定吗?在我测试过的所有地方(GCC 5,clang 3.5,MSVC 14),当主线程退出时,所有分离的线程都会被杀死。
rustyx

3
我认为问题不在于特定的实现,而是如何避免标准定义为未定义的行为。
乔恩·斯潘塞

7
这个答案似乎暗示着,在销毁静态变量之后,进程将进入某种睡眠状态,以等待剩余的线程完成。那是不对的,在exit完成销毁静态对象,运行atexit处理程序,刷新流等之后,它将控制权返回给主机环境,即进程退出。如果一个分离的线程仍在运行(并且通过不接触其自己的线程之外的任何事物而以某种方式避免了未定义的行为),则该线程将在进程退出时消失在烟雾中。
乔纳森·韦基利

3
如果可以使用非ISO C ++ API,那么如果main调用pthread_exit而不是返回或调用exit,则将导致进程等待分离的线程完成,然后exit在最后一个线程完成后调用。
Jonathan Wakely '18

3
“它继续运行(因为标准并没有说它已经停止了)”->谁能告诉我一个线程如何继续执行它的容器过程?
Gupta

42

分离线程

根据std::thread::detach

将执行线程与线程对象分开,允许执行独立继续。线程退出后,将释放所有分配的资源。

来自pthread_detach

pthread_detach()函数应向实现指示,在该线程终止时可以回收该线程的存储。如果线程尚未终止,则pthread_detach()不应使其终止。未指定对同一目标线程的多个pthread_detach()调用的影响。

分离线程主要是为了节省资源,以防应用程序不需要等待线程完成(例如,守护程序,该守护程序必须运行直到进程终止):

  1. 释放应用程序侧句柄:可以使一个std::thread对象超出范围而无需加入,这通常导致std::terminate()对破坏的调用。
  2. 为了使操作系统在线程退出后立即自动清理线程专用资源(TCB),因为我们明确指定了我们对以后再加入线程不感兴趣,因此,一个人不能加入已经分离的线程。

杀死线程

进程终止的行为与主线程的行为相同,这至少可以捕获某些信号。其他线程是否可以处理信号并不那么重要,因为一个线程可以在主线程的信号处理程序调用中加入或终止其他线程。(相关问题

如前所述,任何线程,无论是否分离,都会在大多数OS上死于进程。可以通过发出信号,通过调用exit()或从main函数返回来终止进程本身。但是,C ++ 11无法也不会尝试定义底层OS的确切行为,而Java VM的开发人员肯定可以在某种程度上抽象出这种差异。AFAIK,奇异的过程和线程模型通常在古老的平台(可能不会将C ++ 11移植到该平台)和各种嵌入式系统中找到,它们可能具有特殊和/或有限的语言库实现以及有限的语言支持。

线程支持

如果不支持线程,std::thread::get_id()则应返回一个无效的id(默认构造std::thread::id),因为有一个普通的进程,该进程不需要运行线程对象,并且a的构造函数std::thread应该抛出std::system_error。这就是我如何结合当今的操作系统来理解C ++ 11的方式。如果有一个具有线程支持的OS,但它的进程中没有产生主线程,请告诉我。

控制线程

如果需要控制线程以正确关闭,则可以使用同步原语和/或某种标志来实现。但是,在这种情况下,我倾向于选择在连接后设置关闭标志,因为通过分离线程来增加复杂性没有意义,因为无论如何在同一时间释放资源(std::thread对象的几个字节)与更高的复杂度以及可能更多的同步原语相比应该是可以接受的。


3
因为每个线程都有自己的堆栈(在Linux上为兆字节范围),所以我将选择分离线程(因此一旦退出,它将释放堆栈)并在需要退出主线程时使用一些同步原语(为了正确关闭,它需要加入仍在运行的线程,而不是在返回/退出时终止它们)。
NorbertBérci2014年

8
我真的看不到如何回答这个问题
MikeMB

18

考虑以下代码:

#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()总是如预期打印消息。


此行为完全在Windows上发生。因此,似乎Windows会在程序完成后杀死分离的线程。
Gupta

17

程序退出后线程的命运是不确定的行为。但是现代操作系统将在关闭进程时清理该进程创建的所有线程。

分离an时std::thread,这三个条件将继续成立:

  1. *this 不再拥有任何线程
  2. joinable() 将永远等于 false
  3. get_id() 将等于 std::thread::id()

1
为什么未定义?因为标准没有定义什么?用我的脚注,这不会引起任何detach()不确定的行为吗?难以置信...
Marc Mutz-mmutz

2
@ MarcMutz-mmutz从某种意义上说是不确定的,如果进程退出,则线程的命运是不确定的。
凯撒

2
@Caesar,如何确保在线程完成之前不退出?
MichalH '16


0

为了允许其他线程继续执行,主线程应通过调用pthread_exit()而不是exit(3)来终止。在main中使用pthread_exit很好。当使用pthread_exit时,主线程将停止执行,并将保持僵尸(已取消状态)状态,直到所有其他线程退出。如果在主线程中使用pthread_exit,则无法获取其他线程的返回状态,也无法对其他线程进行清理(可以使用pthread_join(3)完成)。另外,最好分离线程(pthread_detach(3)),以便在线程终止时自动释放线程资源。在所有线程退出之前,不会释放共享资源。


@kgvinod,为什么不添加“ pthread_exit(0);” 在“ ti.detach()”之后;
yshi

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.