如果在其他线程仍在运行时调用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::vector
或std::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会发生什么?的主要内容,如果未能解决你的问题,请参考以下文章