当main()退出时,分离线程会发生什么?

Mar*_*utz 142 c++ multithreading exit c++11 stdthread

假设我正在启动a std::thread然后detach()它,所以线程继续执行,即使std::thread它曾经表示它,超出范围.

进一步假设该程序不具有用于接合分离的线程一个可靠的协议1,所以分离线程仍然运行时main()退出.

我在标准中找不到任何东西(更准确地说,在N3797 C++ 14草案中),它描述了应该发生的事情,1.10和30.3都没有包含相关的措辞.

1另一个可能是等同的问题是:"可以再次连接一个分离的线程",因为你要发明加入的协议,信号部分必须在线程仍在运行时完成,并且OS调度程序可能决定在执行信令之后让线程休眠一小时,接收端无法可靠地检测到线程实际完成.

如果用完main()了卸下运行的线程是不确定的行为,那么任何使用的std::thread::detach()是除非主线程永远不会退出未定义行为2.

因此,运行main()脱离线程的耗尽必须具有已定义的效果.问题是:其中(在C++标准,不POSIX,不OS文档,......)都处于所定义的那些的效果.

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

Mar*_*utz 44

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

它继续运行(因为标准并没有说它已停止),并且这是定义良好的,只要它既不接触其他线程的(自动| 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()条件变量之后将执行更多代码,特别是自动和线程局部对象的析构函数.

运行信号作为线程的最后一件事(自动和线程局部对象的析构函数发生之后)是_at_thread_exit函数族的设计目的.

因此,为了避免在没有任何高于标准要求的实现保证的情况下未定义的行为,您需要(手动)使用_at_thread_exit执行信令的函数连接分离的线程,或者使分离的线程执行对于安全的代码信号处理程序也是.

  • 你确定吗?我测试过的每个地方(GCC 5,clang 3.5,MSVC 14),当主线程退出时,所有分离的线程都被杀死. (15认同)
  • 这个答案似乎暗示在破坏静态变量之后,进程将进入某种休眠状态,等待任何剩余的线程完成.这不是真的,在`exit`完成后销毁静态对象,运行`atexit`处理程序,刷新流等,它将控制权返回给主机环境,即进程退出.如果一个分离的线程仍然在运行(并且通过不触及其自身线程之外的任何东西以某种方式避免了未定义的行为),那么它会在进程退出时消失在一团烟雾中. (5认同)
  • 我认为问题不是具体实现的问题,而是如何避免标准定义为未定义的行为. (3认同)
  • 如果您可以使用非ISO C++ API,那么如果`main`调用`pthread_exit`而不是返回或调用`exit`那么这将导致进程等待分离的线程完成,然后在之后调用`exit`最后一个完成. (3认同)
  • 我认为真正的答案是“行为是未定义的。虽然在大多数操作系统上分离的线程被杀死,但让主进程处理信号是好的”。 (3认同)
  • “它继续运行(因为标准并没有说它已经停止了)”-&gt;谁能告诉我一个线程如何继续执行它的容器过程? (2认同)

Sam*_*Sam 40

分离线程

根据std::thread::detach:

将执行线程与线程对象分开,允许执行独立继续.线程退出后,将释放任何已分配的资源.

来自pthread_detach:

pthread_detach()函数应向实现指示当该线程终止时可以回收线程的存储.如果线程没有终止,pthread_detach()不会导致它终止.未指定多个pthread_detach()调用对同一目标线程的影响.

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

  1. 释放应用程序端句柄:可以让std::thread对象超出范围而不加入,通常会导致std::terminate()对销毁的调用.
  2. 为了允许操作系统在线程退出时自动清理线程特定资源(TCB),因为我们明确指定,我们以后对加入线程不感兴趣,因此,无法加入已经分离的线程.

杀死线程

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

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

线程支持

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

控制线程

如果需要保持对线程的控制以便正确关闭,可以通过使用同步原语和/或某种标志来实现.但是,在这种情况下,设置关闭标志后跟连接是我喜欢的方式,因为通过分离线程没有必要增加复杂性,因为无论如何资源都会被释放,其中std::thread对象的几个字节相对于更高的复杂性和可能更多的同步原语应该是可接受的

  • 我真的不知道这是如何回答这个问题的 (8认同)
  • 由于每个线程都有自己的堆栈(在Linux上的兆字节范围内),我会选择分离线程(因此一旦退出就会释放它的堆栈)并在主线程需要退出时使用一些同步原语(并且为了正确关闭,它需要加入仍在运行的线程,而不是在返回/退出时终止它们). (3认同)

Cae*_*sar 17

程序退出后线程的命运是未定义的行为.但是现代操作系统会在关闭它时清理进程创建的所有线程.

在分离时std::thread,这三个条件将继续保持

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

  • 为什么未定义?因为标准没有定义任何东西?根据我的脚注,对 `detach()` 的任何调用不会有未定义的行为吗?难以置信... (2认同)
  • @ MarcMutz-mmutz在某种意义上它是未定义的,如果进程退出,则线程的命运是未定义的. (2认同)
  • @Caesar以及如何在线程完成之前确保不退出? (2认同)
  • @MichalH 你使用 `join()` (2认同)

Vin*_*nod 17

请考虑以下代码:

#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; 
}
Run Code Online (Sandbox Code Playgroud)

在Linux系统上运行它,从不打印来自thread_fn的消息.操作系统确实会thread_fn()main()退出后立即清理.替换t1.detach()t1.join()始终按预期打印消息.

  • 我尝试使用分离的线程和父级退出来写入文件,认为在父级完成后标准输出可能无法工作。但它也不会写入文件。所以你是对的。 (4认同)
  • 此行为完全发生在 Windows 上。因此,Windows 似乎在程序完成时杀死了分离的线程。 (2认同)

fun*_*unk 6

当主线程(即运行main()函数的线程)终止时,进程终止,所有其他线程停止.

参考:https://stackoverflow.com/a/4667273/2194843