如何干净地退出线程C ++程序?

时间:2018-04-18 06:27:13

标签: c++ multithreading signals exit

我正在程序中创建多个线程。按 Ctrl-C 时,将调用信号处理程序。在信号处理程序中,我最后放了exit(0)。问题是,有时程序安全终止,但有时,我得到运行时错误说明

abort() has been called

那么避免错误的可能解决方案是什么?

4 个答案:

答案 0 :(得分:30)

通常的方法是设置一个 atomic 标志(如std::atomic<bool>),由所有线程(包括主线程)检查。如果设置,则子线程退出,主线程开始join子线程。 然后你可以干净利落地退出。

如果您对线程使用std::thread,则可能是您遇到崩溃的原因。 <{1}}对象被破坏之前,您必须join线程。

答案 1 :(得分:14)

其他人已经提到让信号处理程序设置为std::atomic<bool>并让所有其他线程定期检查该值以知道何时退出。

只要所有其他线程以合理的频率周期性地唤醒,该方法效果很好。

如果你的一个或多个线程完全是事件驱动的,那并不完全令人满意,但是 - 在一个事件驱动的程序中,线程只有在它们有一些工作需要时才会被唤醒,这意味着他们可能会一次睡几天或几周。如果他们被迫每隔(这么多)毫秒被唤醒只是为了轮询一个原子布尔标志,这使得一个非常高CPU效率的程序的CPU效率更低,因为现在每个线程都以很短的规则间隔唤醒, 24/7/365。如果您试图节省电池寿命,这可能会特别成问题,因为它可能会阻止CPU进入省电模式。

避免轮询的另一种方法是:

  1. 启动时,让主线程创建一个fd-pipe或socket-pair(通过调用pipe()socketpair()
  2. 让您的主线程(或可能是其他一些负责的线程)在其准备好的select() fd_set中包含接收套接字(或对poll()采取类似的操作或等待IO的任何操作线程阻塞的功能)
  3. 当执行信号处理程序时,让它写一个字节(任何字节,并不重要)到发送套接字。
  4. 这将导致主线程的select()调用立即返回,FD_ISSET(receivingSocket)由于收到的字节而显示为true
  5. 此时,您的主线程知道该进程退出的时间,因此它可以开始指示其所有子线程开始关闭(通过任何方便的机制;原子布尔值或管道或其他东西)< / LI>
  6. 在告知所有子线程开始关闭之后,主线程应该在每个子线程上调用join(),以便可以保证所有子线程实际上在之前 / em> main()返回。 (这是必要的,因为否则存在竞争条件的风险 - 例如,post-main()清理代码可能偶尔释放资源,而仍在执行的子线程仍在使用它,导致崩溃)

答案 2 :(得分:10)

你必须首先接受的是线程很难。

“使用线程的程序”与“使用内存的程序”一样通用,你的问题类似于“如何使用内存破坏程序中的内存?”

处理线程问题的方法是限制线程的使用方式和线程的行为。

如果您的线程系统是一组组成数据流网络的小型操作,则隐含保证如果操作太大,则会将其分解为较小的操作和/或与系统进行检查点,然后关闭看起来非常不同,如果你有一个线程加载一个外部DLL,然后运行它从1秒到10小时到无限长度。

与C ++中的大多数事情一样,解决您的问题将是关于所有权,控制权和(最后的手段)黑客攻击。

与C ++中的数据一样,每个线程都应该被拥有。线程的所有者应该对该线程有很大的控制权,并且能够告诉它应用程序正在关闭。关闭机制应该是健壮的并且经过测试,并且理想地连接到其他机制(例如早期中止投机任务)。

您调用exit(0)这一事实是一个不好的迹象。这意味着您的 main 执行线程没有干净的关闭路径。从那里开始;中断处理程序应该通知 main 线程应该开始关闭,然后你的主线程应该正常关闭。所有堆栈帧都应该展开,数据应该清理等等。

然后,同样类型的逻辑允许干净和快速关闭也应该应用于您的线程代码。

任何人都告诉你它就像条件变量/原子布尔一样简单,并且轮询正在向你出售货物清单。只有在你运气好的情况下才能在简单的情况下工作,并且确定它是否可靠地工作会非常困难。

答案 3 :(得分:3)

除了一些程序员老兄的回答并且与评论部分的讨论相关,您需要将控制线程终止的标志设为atomic类型。

考虑以下案例:

bool done = false;
void pending_thread()
{
    while(!done)
    {
        std::this_thread::sleep(std::milliseconds(1));
    }
    // do something that depends on working thread results
}

void worker_thread()
{
    //do something for pending thread
    done = true;
}

这里工作线程也可以是你的main线程,done是你线程的终止标志,但是挂起的线程需要在退出之前通过工作线程对给定的数据做一些事情。

此示例包含竞争条件和未定义的行为,并且很难找到现实世界中的实际问题。

现在使用std::automic修正后的版本:

std::atomic<bool> done(false);
void pending_thread()
{
    while(!done.load())
    {
        std::this_thread::sleep(std::milliseconds(1));
    }
    // do something that depends on working thread results
}

void worker_thread()
{
    //do something for pending thread
    done = true;
}

您可以在不考虑竞争条件或UB的情况下退出线程。