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

时间:2013-11-02 16:43:48

标签: c++ multithreading c++11 exit stdthread

假设我正在开始std::thread然后detach(),所以即使曾经代表它的std::thread超出范围,线程也会继续执行。

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

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

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

如果运行脱离线程的main()用完是未定义的行为,则 std::thread::detach()的任何使用都是未定义的行为,除非主线程永远不会退出 2 < / SUP>

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

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

7 个答案:

答案 0 :(得分:41)

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

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

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

当静态对象的破坏完成时会出现问题,因为执行进入只有信号处理程序允许的代码可以执行的状态( [basic.start.term] / 1,第1句) 。在C ++标准库中,只有<atomic>库( [support.runtime] / 9,第二句)。特别是, - 通常 - 排除 condition_variable(它的实现定义是否保存在信号处理程序中使用,因为它不是<atomic>的一部分)。 / p>

除非你在此时解开堆栈,否则很难看出如何避免未定义的行为。

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

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

如问题脚注[2]所述,发信号通知条件变量或信号量或原子计数器不足以加入分离的线程(在确保其执行结束时具有 - 发生在 - 等待线程接收所述信令之前),因为通常会在例如执行之后执行更多代码条件变量的notify_all(),特别是自动和线程局部对象的析构函数。

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

因此,为了避免在没有任何高于标准要求的实现保证的情况下未定义的行为,您需要(手动)使用_at_thread_exit函数加入一个分离的线程来执行信号或< / em>使分离的线程执行代码,这对于信号处理程序也是安全的。

答案 1 :(得分:38)

分离线程

根据std::thread::detach

  

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

来自pthread_detach

  

pthread_detach()函数应指示实现   该线程可以回收线程的存储空间   终止。如果线程没有终止,则pthread_detach()不会终止   导致它终止。多个pthread_detach()调用的效果   在同一目标线程上未指定。

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

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

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

    如前所述,any thread, whether detached or not, will die with its process on most OSes。可以通过调用exit()或从主函数返回来提升信号来终止进程本身。但是,C ++ 11不能也不会尝试定义底层操作系统的确切行为,而Java VM的开发人员肯定可以在一定程度上抽象出这种差异。 AFAIK,异国情调的过程和线程模型通常可以在古代平台上找到(C ++ 11可能不会被移植到其上)和各种嵌入式系统,它们可能具有特殊的和/或有限的语言库实现以及有限的语言支持。

    线程支持

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

    控制线程

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

答案 2 :(得分:17)

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

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

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

答案 3 :(得分:15)

请考虑以下代码:

#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()始终按预期打印消息。

答案 4 :(得分:5)

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

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

答案 5 :(得分:0)

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

答案 6 :(得分:0)

当主进程终止时,该进程创建的所有工作线程也会被杀死。因此,如果 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;
}