在信号处理程序中调用exit时,在全局线程对象上发生std :: exception

时间:2017-01-13 03:26:42

标签: c++ multithreading exception

ctrl+c

时出现以下错误
^Cctrl-c
terminate called without an active exception
Aborted (core dumped)

这是gdb stackstrace:

(gdb) bt
#0  0x0000003a47432625 in raise () from /lib64/libc.so.6
#1  0x0000003a47433e05 in abort () from /lib64/libc.so.6
#2  0x0000003a4a46007d in __gnu_cxx::__verbose_terminate_handler () at ../../.././libstdc++-v3/libsupc++/vterminate.cc:95
#3  0x0000003a4a45e0e6 in __cxxabiv1::__terminate (handler=<optimized out>) at ../../.././libstdc++-v3/libsupc++/eh_terminate.cc:47
#4  0x0000003a4a45e131 in std::terminate () at ../../.././libstdc++-v3/libsupc++/eh_terminate.cc:57
#5  0x000000000040172f in std::thread::~thread() ()
#6  0x00000000004036ad in void std::_Destroy<std::thread>(std::thread*) ()
#7  0x0000000000403396 in void std::_Destroy_aux<false>::__destroy<std::thread*>(std::thread*, std::thread*) ()
#8  0x000000000040311c in void std::_Destroy<std::thread*>(std::thread*, std::thread*) ()
#9  0x0000000000402dd6 in void std::_Destroy<std::thread*, std::thread>(std::thread*, std::thread*, std::allocator<std::thread>&) ()
#10 0x000000000040415b in std::vector<std::thread, std::allocator<std::thread> >::~vector() ()
#11 0x0000003a47435b22 in exit () from /lib64/libc.so.6
#12 0x000000000040142b in f(int) ()
#13 <signal handler called>
#14 0x0000003a478082fb in pthread_join () from /lib64/libpthread.so.0
#15 0x0000003a4a4bb627 in __gthread_join (__value_ptr=0x0, __threadid=<optimized out>)
    at /root/tmp/gcc-4.9.3/x86_64-unknown-linux-gnu/libstdc++-v3/include/x86_64-unknown-linux-gnu/bits/gthr-default.h:668
#16 std::thread::join (this=<optimized out>) at ../../../.././libstdc++-v3/src/c++11/thread.cc:107
#17 0x0000000000401540 in t2() ()
#18 0x0000000000401585 in main ()

这是代码:

vector<thread*> v1;
vector<thread> v2;

void task1(std::string msg){
    while (1) {
      cout << "task1 says: " << msg << endl;
      sleep(2);
    }
}

void ctrl_c(int s)
{
    cout << "ctrl-c\n";
    exit(0);
}

void func1()
{
    for (int i=0; i<3; i++) {
      v1.push_back(new thread(task1, "v1"));
    }
    for (int i=0; i<3; i++) {
        v1[i]->join();
    }
}

void func2()
{
#ifndef GLOBAL
    vector<thread> v2;
#endif
    for (int i=0; i<3; i++) {
      v2.push_back(thread(task1, "bad global v2"));
    }
    for (int i=0; i<3; i++) {
        v2[i].join();
    }
}



int main() { 
    signal(SIGINT,ctrl_c);
    //func1();
    //func2();
    return 0;
}

请注意,v1是全局向量,包含线程指针; v2是全局向量,包含线程对象

当我只运行func1时,程序正常;

当我只运行func2时,根据是否在命令行上给出了GLOBAL选项,情况会有所不同。如果给出,编程正常,如果没有给出,将发生上述异常。此外,如果我注释掉signal(SIGINT,ctrl_c),ctrl + c将不会导致异常。(所以我猜exit调用会导致全局矢量对象破坏,对吗?)

所以我的问题是:这些条件之间的差异是什么?在func2条件下,如果我想捕获SIGINT并在其信号处理程序中调用exit,同时我想使用全局vector<thread>,那么在按下时我是如何避免异常的ctrl + c?

感谢

1 个答案:

答案 0 :(得分:1)

查看回溯中的第12-14项。收到信号后,你在pthread_join()(#12)内(#13)并且上下文切换到信号处理程序(#14)。然后从信号处理程序上下文中调用exit(0),但外部上下文仍在 pthread_join()中。

例如,假设pthread_join()对线程对象进行了锁定,对thread::~thread()的调用尝试对同一线程对象进行锁定。 thread::~thread()在信号处理程序上下文中等待获取在外部上下文中保存的锁...并且外部上下文不可能释放锁,直到执行从信号处理程序返回...并且你有资源僵局。

这不是在这里发生的事情(确切的问题是你的第一个评论者说的;在没有首先分离或加入的线程上调用terminate())。然而,这是一个非常常见的场景,并讲述了为什么在混合信号处理和线程时只需要小心的原因。

首先假设您不应 从信号处理程序中 在手册页中查找信号(man 7 signal),您将找到可在信号处理程序中调用的安全函数列表。您会注意到_exit()已列出但exit()未列出。我将留给您阅读手册页,以确定_exit()exit()之间的主要差异(这里有一个明显的区别)。

所以,不要再解释为什么你的错误代码行为不端的各种原因......我只是建议更合适地使用你的信号处理程序。

通常,我建议您只使用信号处理程序来设置全局标志变量的值。在信号处理程序上下文之外,您可以定期检查标志变量的值,以确定是否已收到信号。然后,您可以在外部上下文中处理信号,而不是在信号处理程序上下文中。

例如(只有一段代码片段,合理地插入代码中):

static volatile int sigterm_caught = 0;

void task1(std::string msg){
    while (sigterm_caught == 0) {
      cout << "task1 says: " << msg << endl;
      sleep(2);
    }
}

void ctrl_c(int s)
{
  if(s == SIGTERM)
    sigterm_caught = 1;
}

另请注意,sleep(2)将在收到信号后提前返回。因此,一旦您的信号处理程序设置sigterm_caught = 1并返回,while(!sigterm_caught)条件将立即在所有线程中进行评估,您的代码很快就会在return 0中由main()正常退出。