【问题标题】:std::exception occur on global thread object when calling exit in signal handler在信号处理程序中调用 exit 时,全局线程对象上发生 std::exception
【发布时间】:2017-01-13 03:26:41
【问题描述】:

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时,程序是ok的;

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

所以我的问题是:这些条件之间有什么区别?在func2条件下,如果我想捕获SIGINT并在其信号处理程序中调用exit,同时我想使用全局vector&lt;thread&gt;,我应该如何避免按ctrl+c时出现异常?

谢谢


【问题讨论】:

  • 是的,exit() 调用全局对象的析构函数。 ~std::thread() 析构函数调用 terminate() 如果被销毁的线程之前没有加入或分离。这就是你观察到的。
  • @IgorTandetnik 但是为什么堆栈跟踪停止在pthread_join,因为它没有加入?
  • @scottxiao 创建了三个线程;这三个都运行一个无限循环。 join 第一次被调用,但由于线程没有终止,所以该调用将永远等待。虽然 join 被阻止(显然在 pthread_join 内部,但这是一个内部实现细节),但用户按下 Ctrl+C,它运行信号处理程序,它调用 exit(),这会破坏全局变量 - 包括其他两个thread 没有调用 join 的对象。
  • @IgorTandetnik 是在 Linux 进程的随机线程上调用的信号处理程序吗?如果是这样,堆栈跟踪可能每次看起来都不一样。
  • @scottxiao 不知道。我自己就是一个 Windows 人。在 Windows 中,它在同一进程中的单独线程上调用。问题中显示的堆栈跟踪似乎表明信号在主线程上运行 - 与调用 join 的线程相同 - 但就像我说的,我真的不知道我在说什么;至少,不在实施层面。

标签: c++ multithreading exception


【解决方案1】:

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

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

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

首先假设您不应该 在信号处理程序中做 任何事情,然后从那里做出深思熟虑的选择。在手册页中查找信号 (man 7 signal),您将找到可以在信号处理程序中调用的安全函数列表。您会注意到 _exit() 已列出,但 exit() 未列出。我将留给您阅读手册页,以确定 _exit()exit() 之间的主要区别(一个明显的区别在这里非常重要)。

因此,与其进一步解释为什么您的不良代码行为不端的各种原因......我只是建议您更适当地使用您的信号处理程序。

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

例如(只有一个代码sn-p,合理地插入到你的代码中):

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) 条件将立即在您的所有线程中进行评估,您的代码将很快通过main() 中的return 0 正常退出。

【讨论】:

  • 谢谢,你是对的。不应该在信号处理程序中使用退出
猜你喜欢
  • 2020-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-12
  • 2019-07-20
  • 2023-03-17
相关资源
最近更新 更多