177

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

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

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

1另一个可能等效的问题是:“可以再次加入分离的线程吗”,因为无论您发明什么协议来加入,信号部分都必须在线程仍在运行时完成,并且操作系统调度程序可能决定在执行信号后立即让线程休眠一个小时,而接收端无法可靠地检测到线程实际上已完成。

如果在运行main()分离线程时用完是未定义的行为,那么任何使用 ofstd::thread::detach()都是未定义的行为,除非主线程永远不会退出2

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

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

4

7 回答 7

55

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

它继续运行(因为标准没有说它停止),这是定义明确的,只要它既不触及其他线程的 (automatic|thread_local) 变量也不触及静态对象。

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

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

于 2015-01-06T09:48:54.093 回答
44

分离线程

根据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),因为有一个普通进程,它不需要线程对象来运行,并且 a 的构造函数std::thread应该抛出 a std::system_error。这就是我结合当今操作系统理解 C++11 的方式。如果有一个支持线程的操作系统,它不会在其进程中产生主线程,请告诉我。

控制线程

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

于 2013-11-02T17:17:54.643 回答
28

考虑以下代码:

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

于 2016-08-07T23:15:33.303 回答
20

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

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

  1. *this不再拥有任何线程
  2. joinable()将始终等于false
  3. get_id()将等于std::thread::id()
于 2013-11-02T16:47:50.030 回答
7

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

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

于 2017-05-03T10:15:22.740 回答
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;
}
于 2021-07-28T16:53:12.483 回答
0

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

于 2019-05-13T08:24:35.203 回答