如果在其他线程仍在运行时调用exit会发生什么?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如果在其他线程仍在运行时调用exit会发生什么?相关的知识,希望对你有一定的参考价值。

假设一个程序有几个线程:t1,t2等。这些是使用pthreads。 t2线程坐在循环读取流中并访问具有静态存储持续时间的变量。

现在假设t1调用exit(0)

(进一步的细节:我有一个程序在基于Unix的系统上执行此操作,并使用g ++编译。程序似乎偶尔会在关闭时崩溃,堆栈跟踪指示静态变量无效。)

  • 线程在C ++对象销毁之前被杀死了吗?
  • C ++是否不知道线程,因此这些线程一直运行直到C ++清理完成?
  • 如果SIGTERM处理程序在继续之前首先关闭或终止线程,或者这是否会自动发生?
答案

我正在回答问题标题中的问题,而不是3个要点,因为我认为要点问题的答案与回答实际问题无关。

当程序处于随机状态时使用exit - 正如您所建议的那样 - 即使使用单个线程,通常也是一种相当野蛮且不确定的方式来结束程序。如果线程在对象破坏之前或之后被破坏也无关紧要,两种方式都会导致噩梦。请记住,每个线程都可以处于随机状态并访问任何内容。并且每个线程的堆栈对象不会被正确销毁。

请参阅exit的文档,了解它的作用和不清理:http://en.cppreference.com/w/cpp/utility/program/exit

我看到的正确关闭多线程程序的方式,它确保没有线程处于随机状态。以某种方式停止所有线程,在可行的情况下在它们上面调用join,并从最后剩余的线程调用exit - 或return,如果在main函数中发生这种情况。

我经常看到的错误方法是正确处理一些对象,关闭一些句柄,并且通常尝试正确关闭直到一切都出错,然后调用terminate。我建议反对。

另一答案

让我试着回答你的问题。伙计们,如果我出错了,请纠正我。

您的程序偶尔会崩溃。这是预期的行为。您已经释放了所有获得的资源。而你的活跃的线程正试图根据它拥有的信息来访问资源。如果成功,它将运行。如果不成功,它会崩溃。

这种行为往往是零星的。如果操作系统将释放的资源分配给其他进程,或者它使用了资源,那么您将看到线程崩溃。如果没有,你的线程就会运行。此行为取决于操作系统,硬件,RAM,当进程死亡时使用的资源的百分比。任何过度使用资源等等

线程在C ++对象销毁之前被杀死了吗?没有.C ++没有任何内置的线程支持。 P线程只是posix线程,它与底层操作系统一起工作,并为您提供创建线程的功能(如果需要)。从技术上讲,由于线程不是C ++的一部分,因此无法自动杀死线程。如果我错了,请纠正我。

C ++是不是意识到线程,所以这些线程一直运行直到C ++清理完成? C ++不知道线程。对于C ++ 11来说,这是不可能的

如果SIGTERM处理程序在继续之前首先关闭或终止线程,还是会自动执行此操作?从技术上讲,SIGTERM处理程序不应该杀死线程。为什么要操作系统处理程序杀死正在运行的线程?每个操作系统都在硬件上工作,为用户提供功能。不要杀死任何正在运行的进程。好吧,程序员必须将线程加入到main中,但是在某些情况下,您可能希望让线程运行一段时间。也许。

软件开发人员/供应商有责任编写不会崩溃或最终无限循环的代码,并在需要时终止所有正在运行的线程。操作系统不能承担这些行为的责任。这就是为什么Windows / Apple为他们的操作系统认证某些软件的原因。因此,客户可以放心购买。

另一答案

从C ++ 11开始,我们有std::thread,所以从那以后我们可以说C ++知道线程。但是,那些可能不是pthreads(它在Linux下,但它是一个实现细节),你特别提到你使用了pthreads ...

我想从kris的答案中补充一点的事实是,处理线程实际上比最初认为的要复杂一些。在大多数情况下,人们为RAII创建一个类(资源获取是初始化。)

这是一个内存块的例子,为了保持简单(但考虑在C ++中使用std::vectorstd::array进行缓冲区管理):

class buffer
{
public:
    buffer()
        : m_buffer(new char[1024])
    {
    }

    ~buffer()
    {
        delete [] m_buffer;
    }

    char * data()
    {
        return m_buffer;
    }

private:
    char * m_buffer;
};

这个类的作用是管理m_buffer指针的生命周期。在构造时它分配缓冲区并在销毁时释放它。到此为止,没什么新鲜的。

但是,对于线程,你有一个潜在的问题,因为线程运行的类必须在它被销毁之前保持良好的状态,并且没有那么多的C ++程序员知道,一旦你到达析构函数,做某些事情为时已晚。 ..具体来说,调用任何虚函数。

所以下面的基本类实际上是不正确的:

// you could also hide this function inside the class, see "class thread" below
class runner;
void start_func(void * data)
{
    ((runner *) data)->run();
}

class runner
{
public:
    runner()
    {
        // ...setup attr...
        m_thread = pthread_create(&m_thread, &attr, &start_func, this);
    }

    virtual ~runner()
    {
        stop();   // <-- virtual function, we may be calling the wrong one!
        pthread_join(m_thread);
    }

    virtual void run() = 0;

    virtual void stop()
    {
        m_stop = true;
    }

private:
    pthread_t m_thread;
    bool m_stop = false;
};

这是错误的,因为stop()函数可能需要调用派生版本的类中定义的某个虚函数。此外,您的run()函数很可能在它存在之前使用虚函数。其中一些可能是纯虚函数。在~runner()函数中调用那些被称为最终将使用std::terminate()

这个问题的解决方案是有两个类。一个有run()纯虚函数和一个线程的跑步者。线程类负责在pthread_join()之后删除跑步者。

重新定义运行器以不包含有关pthread的任何内容:

class runner
{
public:
    virtual void run() = 0;

    virtual void stop()
    {
        m_stop = true;
    }

private:
    bool m_stop = false;
};

线程类处理stop(),它可以在析构函数中发生:

class thread
{
public:
    thread(runner *r)
        : m_runner(r)
    {
        // ...setup attr...
        m_thread = pthread_create(&m_thread, &attr, &start_func, this);
    }

    ~thread()
    {
        stop();
    }

    void stop()
    {
        // TODO: make sure that a second call works...
        m_runner->stop();
        pthread_join(m_thread);
    }

private:
    static void start_func(void * data)
    {
        ((thread *) data)->start();
    }

    void start()
    {
        m_runner->run();
    }

    runner * m_runner;
    pthread_t m_thread;
};

现在,当你想要使用你的跑步者时,你重载它并实现一个run()函数:

class worker
    : runner
{
public:
    virtual void run()
    {
        ...do heavy work here...
    }
};

最后,当您确保首先删除该线程时,您可以安全地使用它。这意味着定义了第二个(由于你需要将一个运行器传递给线程,所以这些类强制执行!)

int main()
{
    worker w;
    thread t(&w);
    ...do other things...
    return 0;
}

现在在这种情况下C ++负责清理,但仅仅因为我使用的是return而不是exit()

但是,您的问题的解决方案是例外。我的main()也是例外安全!在调用std::terminate()之前,线程将被彻底停止(因为我没有try / catch,它将终止)。

“从任何地方退出”的一种方法是创建一个允许您这样做的例外。所以main()会变成这样:

int main()
{
    try
    {
        worker w;
        thread t(&w);
        ...do other things...
    }
    catch(my_exception const & e)
    {
        exit(e.exit_code());
    }
    return 0;
}

我相信很多人会评论你不应该使用例外来退出你的软件这一事实。事实是我的大多数软件都有这样的try / catch所以我至少可以记录发生的错误,这与使用“退出异常”非常相似。

警告:std::thread与上面的thread类不同。它接受一个函数指针来执行一些代码。它会在破坏时调用pthread_join()(至少在g ++ Linux上)。但是,它不会告诉您的线程代码。如果你需要听一些信号知道它必须退出,你就是负责人。这是一种完全不同的思维方式,但是,使用它也是安全的(在缺失的信号之外)。

要获得完整的实现,您可能需要在Snap中查看我在Github上的snap_thread.cpp/h! C ++项目。我的实现包括更多功能,特别是它有一个FIFO,您可以使用它安全地将工作负载传递给您的线程。

分离线程怎么样?

我也用了一段时间。事实是,只有pthread_join()是100%安全的。分离意味着线程仍在运行,主进程退出可能会使线程崩溃。虽然在我的最后我会告诉线程退出并等待“完成”信号设置,它仍然会偶尔崩溃。我可能需要3个月左右才能看到无法解释的崩溃,但它会发生。由于我删除了它并且总是使用join,我没有看到那些无法解释的崩溃。很好地证明您不想使用该特殊的分离线程功能。

以上是关于如果在其他线程仍在运行时调用exit会发生什么?的主要内容,如果未能解决你的问题,请参考以下文章

在线程上多次调用 start() 会发生啥[关闭]

当标签在运行时,HTML5 Web工作者线程发生了啥?

在 AsyncTask 仍在后台运行的情况下,Activity.finish() 会发生啥?

窗口关闭后 PyQt 线程仍在运行

如何在旧版本仍在运行时调试 Discord 机器人?

如果某些线程比其他线程先结束会发生啥?