【问题标题】:How to terminate a function call after a timeout?超时后如何终止函数调用?
【发布时间】:2020-05-22 17:12:49
【问题描述】:

假设我有一个 foo() 函数。例如,我希望它运行 5 秒,然后必须取消它并继续执行程序的其余部分。

代码sn-ps:

int main() {
    // Blah blah
    foo(); // Running in 5 sec only
    // After 5 sec, came here and finished
}

参考:在 StackOverflow 上搜索了一段时间后,我发现这是我需要但用 python 编写的:Timeout on a function call

signal.hunistd.h 可以关联。

【问题讨论】:

  • 是函数负责决定它花费了太长时间,还是另一个外部线程决定并需要告诉函数停止?
  • 您是否需要在此期间保留foo() 产生的任何副作用,或者您是否可以丢弃任何部分结果并声明花费了太长时间?
  • 我想我想在需要终止 foo() 时处理另一个函数。 @1201ProgramAlarm
  • @idclev463035818:有时单元测试代码具有此属性。有时某些数值计算必须在一段时间后中断。但是对于前者,您将部分运行视为失败,因此您不关心部分结果。对于后者,例程本身会监控它是否应该以部分计算结束(例如,结果没有收敛)。
  • @jxh 用于单元测试我不会关心foo 的不干净终止。另一方面,对于数值计算,我永远不想简单地终止它,而是包括一种提前终止的方法 inside foo 这样我就可以轻松地收集有关计算状态的信息时间到。不管是什么,恕我直言,一个好的解决方案取决于foo 实际上是什么以及它为什么需要超时

标签: c++ multithreading timeout


【解决方案1】:

这可以通过线程来实现。从 C++20 开始,这将相当简单:

{
    std::jthread t([](std::stop_token stoken) {
        while(!stoken.stop_requested()) {
            // do things that are not infinite, or are interruptible
        }
    });

    using namespace std::chrono_literals;
    std::this_thread::sleep_for(5s);
}

请注意,与操作系统的许多交互都会导致进程被“阻塞”。一个例子是 POSIX 函数listen,它等待传入的连接。如果线程被阻塞,那么它将无法进行下一次迭代。

不幸的是,C++ 标准没有指定此类特定于平台的调用是否应被停止请求中断。您需要使用特定于平台的方法来确保发生这种情况。通常,可以将信号配置为中断阻塞系统调用。对于listen,一个选项是连接到等待套接字。

【讨论】:

  • 您能解释一下您的代码或提供一些参考吗?谢谢。
【解决方案2】:

在 C++ 中无法统一执行此操作。当您使用特定于操作系统的 API 时,有一些方法可以取得一定程度的成功,但是这一切都变得非常麻烦。

您可以在 *nix 中使用的基本思想是 alarm() 系统调用和 setjmp/longjmp C 函数的组合。

一个(伪)代码:

std::jmp_buf jump_buffer;

void alarm_handle(int ) {
    longjmp(jump_buffer);
}

int main() {
   signal(SIGALRM, alarm_handle); 
    alarm(5);
    if (setjmp(jump_buffer)) {
        foo(); // Running in 5 sec only
     } else {
        // After 5 sec, came here and finished
        // if we are here, foo timed out
     } 
}

这一切都非常脆弱和不稳定(即长跳转不能很好地适应 C++ 对象的生命周期),但如果你知道你在做什么,这可能会起作用。

【讨论】:

  • 这会绕过foo() 中的任何析构函数,并且可能会导致未定义的行为,因为信号可能已在foo() 位于其他库函数中时传递,并且else 案例试图进入相同的功能。
  • 如果foo 在它响起之前完成,您还需要“使警报静音”,这也将打开一个窗口,foo 已在其中完成,但警报仍然会在它响起之前响起沉默。
  • @jxh 我在回答中也提到了绕过析构函数,这里没有专门触发未定义行为的内容。
  • @1201ProgramAlarm 使警报静音应该在 foo 完成后发生。
  • @SergeyA 假设您无法更改 foo。否则,如果您可以更改 foo,您可以实施任意数量的更好的解决方案。
【解决方案3】:

完全标准的 C++11

#include <iostream>
#include <thread>         // std::this_thread::sleep_for
#include <chrono>         // std::chrono::seconds

using namespace std;

// stop flag
bool stopfoo;

// function to run until stopped
void foo()
{
    while( ! stopfoo )
    {
        // replace with something useful
        std::this_thread::sleep_for (std::chrono::seconds(1));
        std::cout << "still working!\n";
    }
    std::cout "stopped\n";
}

// function to call a top after 5 seconds
void timer()
{
    std::this_thread::sleep_for (std::chrono::seconds( 5 ));
    stopfoo = true;
}

int main()
{
    // initialize stop flag
    stopfoo = false;

    // start timer in its own thread
    std::thread t (timer);

    // start worker in main thread
    foo();

    return 0;
}

这与线程安全停止标志相同(不是必需的,但对于更复杂的情况来说是一种很好的做法)

#include <iostream>
#include <thread>         // std::this_thread::sleep_for
#include <chrono>         // std::chrono::seconds
#include <mutex>

using namespace std;

class cFlagThreadSafe
{
public:
    void set()
    {
        lock_guard<mutex> l(myMtx);
        myFlag = true;
    }
    void unset()
    {
        lock_guard<mutex> l(myMtx);
        myFlag = false;
    }
    bool get()
    {
         lock_guard<mutex> l(myMtx);
         return myFlag;
    }
private:
    bool myFlag;
    mutex myMtx;
};

// stop flag
cFlagThreadSafe stopfoo;

// function to run until stopped
void foo()
{
    while( ! stopfoo.get() )
    {
        // replace with something useful
        this_thread::sleep_for (std::chrono::seconds(1));
        cout << "still working!\n";
    }
    cout << "stopped\n";
}

// function to call a top after 5 seconds
void timer()
{
    this_thread::sleep_for (chrono::seconds( 5 ));
    stopfoo.set();
}

int main()
{
    // initialize stop flag
    stopfoo.unset();

    // start timer in its own thread
    thread t (timer);

    // start worker in main thread
    foo();

    t.join();

    return 0;
}

如果可以在主线程中做所有事情,事情可以大大简化。

#include <iostream>
#include <thread>         // std::this_thread::sleep_for
#include <chrono>         // std::chrono::seconds

using namespace std;

void foo()
{
    auto t1 = chrono::steady_clock ::now();

    while( chrono::duration_cast<chrono::seconds>(
                chrono::steady_clock ::now() - t1 ).count() < 5 )
    {
        // replace with something useful
        this_thread::sleep_for (std::chrono::seconds(1));
        cout << "still working!\n";
    }
    cout << "stopped\n";
}

int main()
{
    // start worker in main thread
    foo();

    return 0;
}

【讨论】:

  • stopfoo 应该是原子的。
  • 其实应该用互斥锁保护。然而,在实践中是没有关系的,这个演示软件不需要它。如果 OP 不知道如何使用互斥锁,他们应该问另一个问题。
  • 恕我直言 std::async() 和所有其余的都是不必要的复杂性。我从来没有发现它需要它。
  • 编译器当然可以决定循环不修改变量,然后调整发出的代码以将变量视为常量。通常,您使用volatile 来避免这种情况。您使用原子来防止代码对部分写入的值进行读取,因为这可能会导致未定义的行为(例如,如果部分读取被解码为陷阱值)。
  • @jxh 我知道编译器可以优化循环,如果它确定条件没有改变并且使用 volatile 关键字来防止这种优化。我不明白的是编译如何优化 this 示例中的 while 循环(据我所知,这种优化通常发生在条件为 const 的情况下,这里不是这种情况!)。请问你能详细说明吗?同样关于“部分读取”,在什么平台上可以在我们在这里讨论的布尔值上发生部分读取?
猜你喜欢
  • 2011-09-26
  • 1970-01-01
  • 2012-01-17
  • 1970-01-01
  • 2015-06-27
  • 1970-01-01
  • 2022-11-18
  • 2012-08-13
  • 1970-01-01
相关资源
最近更新 更多