简体   繁体   English

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

[英]How to cleanly exit a threaded C++ program?

I am creating multiple threads in my program. 我正在我的程序中创建多个线程。 On pressing Ctrl-C , a signal handler is called. Ctrl-C时 ,将调用信号处理程序。 Inside a signal handler, I have put exit(0) at last. 在信号处理程序内部,我已经将exit(0)放在最后。 The thing is that sometimes the program terminates safely but the other times, I get runtime error stating 问题是,有时程序安全终止,但有时,我得到运行时错误说明

abort() has been called

So what would be the possible solution to avoid the error? 那么避免错误的可能解决方案是什么?

The usual way is to set an atomic flag (like std::atomic<bool> ) which is checked by all threads (including the main thread). 通常的方法是设置一个原子标志(如std::atomic<bool> ),由所有线程(包括主线程)检查。 If set, then the sub-threads exit, and the main thread starts to join the sub-threads. 如果设置,则子线程退出,主线程开始join子线程。 Then you can exit cleanly. 然后你可以干净利落地退出。

If you use std::thread for the threads, that's a possible reason for the crashes you have. 如果您对线程使用std::thread ,那么这可能是您崩溃的原因。 You must join the thread before the std::thread object is destructed. 您必须在std::thread对象被破坏之前join该线程。

Others have mentioned having the signal-handler set a std::atomic<bool> and having all the other threads periodically check that value to know when to exit. 其他人提到让信号处理程序设置为std::atomic<bool>并让所有其他线程定期检查该值以知道何时退出。

That approach works well as long as all of your other threads are periodically waking up anyway, at a reasonable frequency. 只要所有其他线程以合理的频率周期性地唤醒,那么这种方法效果很好。

It's not entirely satisfactory if one or more of your threads is purely event-driven, however -- in an event-driven program, threads are only supposed to wake up when there is some work for them to do, which means that they might well be asleep for days or weeks at a time. 如果你的一个或多个线程纯粹是事件驱动的,那就不完全令人满意了 - 但是在一个事件驱动的程序中,线程只有在它们有一些工作要做时才会被唤醒,这意味着它们可能很好一次睡几天或几周。 If they are forced to wake up every (so many) milliseconds simply to poll an atomic-boolean-flag, that makes an otherwise extremely CPU-efficient program much less CPU-efficient, since now every thread is waking up at short regular intervals, 24/7/365. 如果他们被迫每隔(这么多)毫秒被唤醒只是为了轮询一个原子布尔标志,这使得一个非常高CPU效率的程序的CPU效率更低,因为现在每个线程都以很短的规则间隔唤醒, 24/7/365。 This can be particularly problematic if you are trying to conserve battery life, as it can prevent the CPU from going into power-saving mode. 如果您试图节省电池寿命,这可能会特别成问题,因为它可能会阻止CPU进入省电模式。

An alternative approach that avoids polling would be this one: 避免轮询的另一种方法是:

  1. On startup, have your main thread create an fd-pipe or socket-pair (by calling pipe() or socketpair() ) 在启动时,让主线程创建一个fd-pipe或socket-pair(通过调用pipe()socketpair()
  2. Have your main thread (or possibly some other responsible thread) include the receiving-socket in its read-ready select() fd_set (or take a similar action for poll() or whatever wait-for-IO function that thread blocks in) 让您的主线程(或可能是其他一些负责的线程)在其read-ready select() fd_set中包含接收套接字(或对poll()或线程阻塞的任何等待IO函数采取类似的操作)
  3. When the signal-handler is executed, have it write a byte (any byte, doesn't matter what) into the sending-socket. 当执行信号处理程序时,让它将一个字节(任何字节,无关紧要)写入发送套接字。
  4. That will cause the main thread's select() call to immediately return, with FD_ISSET(receivingSocket) indicating true because of the received byte 这将导致主线程的select()调用立即返回,因为收到的字节, FD_ISSET(receivingSocket)指示为true
  5. At that point, your main thread knows it is time for the process to exit, so it can start directing all of its child threads to start shutting down (via whatever mechanism is convenient; atomic booleans or pipes or something else) 此时,您的主线程知道该进程退出的时间,因此它可以开始指示其所有子线程开始关闭(通过任何方便的机制;原子布尔值或管道或其他东西)
  6. After telling all the child threads to start shutting down, the main thread should then call join() on each child thread, so that it can be guaranteed that all of the child threads are actually gone before main() returns. 在告诉所有子线程开始关闭之后,主线程应该在每个子线程上调用join() ,这样可以保证所有子线程 main()返回之前实际上已经消失。 (This is necessary because otherwise there is a risk of a race condition -- eg the post-main() cleanup code might occasionally free a resource while a still-executing child thread was still using it, leading to a crash) (这是必要的,因为否则存在竞争条件的风险 - 例如,post-main()清理代码可能偶尔释放资源,而仍在执行的子线程仍在使用它,导致崩溃)

The first thing you must accept is that threading is hard. 你必须接受的第一件事是线程很难。

A "program using threading" is about as generic as a "program using memory", and your question is similar to "how do I not corrupt memory in a program using memory?" “使用线程的程序”与“使用内存的程序”一样通用,你的问题类似于“如何使用内存破坏程序中的内存?”

The way you handle threading problem is to restrict how you use threads and the behavior of the threads. 处理线程问题的方法是限制线程的使用方式和线程的行为。

If your threading system is a bunch of small operations composed into a data flow network, with an implicit guarantee that if an operation is too big it is broken down into smaller operations and/or does checkpoints with the system, then shutting down looks very different than if you have a thread that loads an external DLL that then runs it for somewhere from 1 second to 10 hours to infinite length. 如果您的线程系统是由数据流网络组成的一堆小操作,则隐含保证如果操作太大,则会将其分解为较小的操作和/或与系统进行检查点,然后关闭看起来非常不同比如果你有一个线程加载一个外部DLL然后运行它从1秒到10小时到无限长度的某个地方。

Like most things in C++, solving your problem is going to be about ownership, control and (at a last resort) hacks. 像C ++中的大多数东西一样,解决你的问题将是关于所有权,控制和(最后的手段)黑客攻击。

Like data in C++, every thread should be owned. 与C ++中的数据一样,每个线程都应该被拥有。 The owner of a thread should have significant control over that thread, and be able to tell it that the application is shutting down. 线程的所有者应该对该线程有很大的控制权,并且能够告诉它应用程序正在关闭。 The shut down mechanism should be robust and tested, and ideally connected to other mechanisms (like early-abort of speculative tasks). 关闭机制应该是健壮的并且经过测试,并且理想地连接到其他机制(例如早期中止投机任务)。

The fact you are calling exit(0) is a bad sign. 你调用exit(0)的事实是一个坏兆头。 It implies your main thread of execution doesn't have a clean shutdown path. 它意味着您的主要执行线程没有干净的关闭路径。 Start there; 从那里开始; the interrupt handler should signal the main thread that shutdown should begin, and then your main thread should shut down gracefully. 中断处理程序应该通知线程应该开始关闭,然后你的主线程应该正常关闭。 All stack frames should unwind, data should be cleaned up, etc. 所有堆栈帧都应该展开,数据应该清理,等等。

Then the same kind of logic that permits that clean and fast shutdown should also be applied to your threaded off code. 然后,同样类型的逻辑允许干净和快速关闭也应该应用于您的线程代码。

Anyone telling you it is as simple as a condition variable/atomic boolean and polling is selling you a bill of goods. 有人告诉你它就像条件变量/原子布尔一样简单,并且轮询正在向你出售货物清单。 That will only work in simple cases if you are lucky, and determining if it works reliably is going to be quite hard. 如果你很幸运,那只会在简单的情况下起作用,并且确定它是否可靠地工作会非常困难。

Additional to Some programmer dude answer and related to discussion in the comment section, you need to make the flag that controls termination of your threads as atomic type. 除了一些程序员老兄的回答以及与评论部分中的讨论相关的内容之外,您还需要将控制线程终止的标志作为atomic类型。

Consider following case : 考虑以下案例:

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;
}

Here worker thread can be your main thread also and done is terminating flag of your thread, but pending thread need to do something with given data by working thread, before exiting. 这里工作线程也可以是你的main线程,并且done了你的线程的终止标志,但是挂起的线程需要在退出之前通过工作线程对给定数据执行某些操作。

this example has race condition and undefined behaviour along with it, and it's really hard to find what is the actual problem int the real world. 这个例子有竞争条件和未定义的行为,并且很难找到真实世界中的实际问题。

Now the corrected version using std::automic : 现在使用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;
}

You can exit thread without being concern of race condition or UB. 您可以在不考虑竞争条件或UB的情况下退出线程。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM