Main ()退出时,分离的线程会发生什么情况?

假设我先启动一个 std::thread,然后再启动一个 detach(),这样即使曾经代表它的 std::thread超出了作用域,线程也会继续执行。

进一步假设程序没有一个可靠的协议来连接分离的线程 1,因此当 main()退出时,分离的线程仍然运行。

我在标准(更确切地说,在 N3797 C + + 14草案中)中找不到任何描述应该发生什么的内容,1.10和30.3都没有相关的措辞。

1 另一个可能等价的问题是: “一个分离的线程能否再次被连接”,因为无论你发明什么协议来连接,信令部分必须在线程仍在运行时完成,而操作系统调度程序可能决定在信令执行之后让线程休眠一个小时,而接收端无法可靠地检测到线程实际上已经完成。

如果在运行分离线程的情况下用完 main()是未定义的行为,那么 任何使用 std::thread::detach()就是未定义的行为,除非主线程从未退出 2

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

2 不能连接分离的线程(std::thread::join()意义上的)。可以等待来自分离线程的结果(例如通过 std::packaged_task的将来,或者通过计数信号量、标志和条件变量) ,但是这并不能保证 线程已完成执行。实际上,除非你把信令部分放入线程的第一个自动对象的析构函数中,否则 威尔通常是运行 之后信令代码的代码(析构函数)。如果操作系统调度主线程使用结果并在分离的线程完成运行所述析构函数之前退出,那么 ^ Wis 将会发生什么?

73497 次浏览

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

当分离 std::thread时,这三个条件将继续有效:

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

分线

根据 std::thread::detach:

将执行线程与线程对象分离,允许 独立地继续执行。任何分配的资源将 线程退出时释放。

来自 pthread_detach:

Pthread _ detach ()函数应指示实现 线程的存储空间可以回收时,该线程 如果线程没有终止,pthread _ detach ()将不能 多个 pthread _ detach ()调用的效果 是未指定的。

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

  1. 释放应用程序端句柄: 可以让 std::thread对象不加入就超出范围,这通常会导致在销毁时调用 std::terminate()
  2. 允许操作系统在线程退出时自动清理特定于线程的资源(TCB) ,因为我们显式地指定,我们不想在以后加入线程,因此,不能加入已经分离的线程。

杀线

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

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

线程支持

如果不支持线程,那么 std::thread::get_id()应该返回一个无效的 id (默认构造为 std::thread::id) ,因为有一个普通进程,它不需要一个线程对象来运行,而 std::thread的构造函数应该抛出一个 std::system_error。这就是我对 C + + 11和今天的操作系统的理解。如果有一个操作系统支持线程处理,它不会在其进程中产生一个主线程,请让我知道。

控制线程

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

对于最初的问题“当 main()退出时分离的线程会发生什么情况”,答案是:

它继续运行(因为标准没有说它已经停止) ,这是定义良好的,只要它既不接触其他线程的变量(自动 | thread _ local) ,也不接触静态对象。

这似乎允许线程管理器作为静态对象(请注意,在 [ basic.start.term ]/4中也是这样说的,这要感谢@dyp 作为指针)。

当静态对象的销毁完成时,问题就出现了,因为执行进入了一个只有信号处理程序中允许的代码才能执行的状态([ basic.start.term ]/1,第一句)。在所有 C++标准程式库中,这只是 <atomic>库([ support. run ]/9,第2句)。特别是,一般来说,不包括 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函数连接起来,执行信令 或者,使分离的线程执行 只有代码,这对于信号处理器来说也是安全的。

考虑以下代码:

#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 发出的消息。当 main()退出时,操作系统确实清理了 thread_fn()。用 t1.join()替换 t1.detach()总是按预期打印消息。

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

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

为了允许其他线程继续执行,主线程应该通过调用 pthread _ exit ()而不是 exit (3)来终止。 在 main 中使用 pthread _ exit 是可以的。当使用 pthread _ exit 时,主线程将停止执行,并保持僵尸(失效)状态,直到所有其他线程退出。 如果在主线程中使用 pthread _ exit,则无法获得其他线程的返回状态,也无法清理其他线程(可以使用 pthread _ join (3))。另外,最好分离线程(pthread _ detach (3)) ,这样线程资源在线程终止时自动释放。在所有线程退出之前,不会释放共享资源。

当主进程终止时,由该进程创建的所有辅助线程也将被终止。因此,如果 main()在它创建的分离线程完成执行之前返回,那么分离的线程将被操作系统杀死。举个例子:

void work(){
this_thread::sleep_for(chrono::seconds(2));
cout<<"Worker Thread Completed"<<endl;
}
int main(){
thread t(work);
t.detach();
cout<<"Main Returning..."<<endl;
return 0;
}

在上面的程序 Worker Thread Completed将永远不会被打印。因为 main在辅助线程的2秒延迟之前返回。现在,如果我们稍微改变一下代码,并在 main返回之前添加一个大于2秒的延迟。比如:

void work(){
this_thread::sleep_for(chrono::seconds(2));
cout<<"Worker Thread Completed"<<endl;
}
int main(){
thread t(work);
t.detach();
cout<<"Main Returning..."<<endl;
this_thread::sleep_for(chrono::seconds(4));
return 0;
}

输出

Main Returning...
Worker Thread Completed

现在,如果一个线程是从除 main之外的任何函数创建的,那么分离的线程将保持活动状态,直到它的执行完成,即使在函数返回之后。例如:

void child()
{
this_thread::sleep_for(chrono::seconds(2));
cout << "Worker Thread Completed" << endl;
}
void parent(){
thread t(child);
t.detach();
cout<<"Parent Returning...\n";
return;
}
int main()
{
parent();
cout<<"Main Waiting..."<<endl;
this_thread::sleep_for(chrono::seconds(5));
}

输出

Parent Returning...
Main Waiting...
Worker Thread Completed

使 main在返回之前等待分离的辅助线程的一种解决办法是使用 condition_variable。例如:

#include <bits/stdc++.h>
using namespace std;
condition_variable cv;
mutex m;
void work(){
this_thread::sleep_for(chrono::seconds(2));
cout << "Worker Thread Completed" << endl;
cv.notify_all();
}
int main(){
thread t(work);
t.detach();
cout << "Main Returning..." << endl;
unique_lock<mutex>ul(m);
cv.wait(ul);
return 0;
}