假设程序有多个线程:t1,t2等。这些是使用pthreads。 t2线程坐在循环读取流中并访问具有静态存储持续时间的变量。
现在假设t1调用exit(0)
。
(进一步的细节:我有一个程序在基于Unix的系统上执行此操作,并使用g ++编译。程序似乎偶尔会在关闭时崩溃,堆栈跟踪表明静态变量无效。)
线程在C ++对象销毁之前被杀死了吗?
C ++是否不知道线程,所以这些线程一直运行直到C ++清理完成?
SIGTERM
处理程序在继续之前是先关闭还是终止线程,还是自动发生?
答案 0 :(得分:4)
我正在回答问题标题中的问题,而不是3个要点,因为我认为要点问题的答案与回答实际问题无关。
当程序处于随机状态时使用exit
- 正如您所建议的那样 - 即使使用单个线程,通常也是一种相当野蛮且不确定的方式来结束程序。如果线程在对象破坏之前或之后被破坏也无关紧要,两种方式都会导致噩梦。请记住,每个线程都可以处于随机状态并访问任何内容。并且每个线程的堆栈对象都不会被正确销毁。
请参阅exit
的文档,了解它的作用和不清理:
http://en.cppreference.com/w/cpp/utility/program/exit
我看到正确关闭多线程程序的优势方式,确保没有线程处于随机状态。以某种方式停止所有线程,在可行的情况下在其上调用join
,并在最后剩余的线程调用exit
- 或return
(如果在主函数中发生这种情况)。 / p>
我经常看到的一种不正确的方法是正确处理一些对象,关闭一些句柄,并且通常尝试正确关闭,直到出现问题,然后调用terminate
。我建议反对。
答案 1 :(得分:2)
让我试着回答你的问题。伙计们,如果我出错了,请纠正我。
您的程序偶尔会崩溃。 这是预期的行为。您已经释放了所有获得的资源。而你的活跃的线程正试图根据它拥有的信息来访问资源。如果成功,它将运行。如果不成功,它会崩溃。
这种行为往往是零星的。如果操作系统将释放的资源分配给其他进程,或者它使用了资源,那么您将看到线程崩溃。如果没有,你的线程就会运行。此行为取决于操作系统,硬件,RAM,当进程死亡时使用的资源的百分比。任何过度使用资源等等。
线程在C ++对象销毁之前被杀死了吗? 没有.C ++没有任何内置的线程支持。 P线程只是posix线程,它与底层操作系统一起工作,并为您提供创建线程的功能(如果需要)。从技术上讲,由于线程不是C ++的一部分,因此无法自动杀死线程。如果我错了,请纠正我。
C ++是不是知道线程,所以这些线程一直运行直到C ++清理完成? C ++不知道线程。对于C ++ 11来说,这是不可能的。
SIGTERM处理程序在继续之前是先关闭还是终止线程,还是会自动发生? 从技术上讲,SIGTERM处理程序不应该杀死线程。为什么要操作系统处理程序杀死正在运行的线程?每个操作系统都在硬件上工作,为用户提供功能。不要杀死任何正在运行的进程。好吧,程序员必须将线程加入到main中,但是在某些情况下,您可能希望让线程运行一段时间。也许。
软件开发人员/供应商有责任编写不会崩溃或最终无限循环的代码,并在需要时终止所有正在运行的线程。操作系统不能承担这些行为的责任。 这就是为什么Windows / Apple为他们的操作系统认证某些软件的原因。因此,客户可以放心购买。
答案 2 :(得分:0)
自C ++ 11起,我们有了std::thread
,因此从那时起,我们可以说C ++ 了解线程。但是,这些可能不是pthreads(在Linux下,但这是实现细节),您特别提到使用过pthreads ...
我想从kris的答案中补充一件事,那就是事实是,处理线程实际上比第一个认为的要复杂一些。在大多数情况下,人们为RAII(资源获取就是初始化)创建一个类。
这里有一个带有一块内存的示例,以使其保持简单(但考虑使用std::vector
或std::array
进行C ++中的缓冲区管理)
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()
的之后删除运行器。
runner被重新定义为不包含有关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,因此看不到那些无法解释的崩溃。一个很好的证明,您不想使用该特殊的分离线程功能。