【问题标题】:How to implement timeout for function in c++如何在 C++ 中实现函数超时
【发布时间】:2017-03-25 20:18:51
【问题描述】:

我有函数 f; 我想在启动 f 后抛出异常 1s。 我无法修改 f()。用c++可以吗?

try {
   f();
}
catch (TimeoutException& e) {
//timeout
}

【问题讨论】:

  • 可以修改代码调用f吗? IE。 try 块内的代码?
  • 是的,我可以修改调用 f 的代码
  • 你如何实现它取决于f() 做了什么。你不能只抢占一个正在运行的函数。
  • 否 - 一旦您的代码输入 f(),您将不再处于控制之中。听起来像是 X/Y 问题,你真正想要实现什么?

标签: c++ c++11 time timeout


【解决方案1】:

您可以创建一个单独的线程来运行调用本身,并在主线程中等待返回的条件变量,一旦返回,调用f 的线程将发出信号。诀窍是在 1s 超时的情况下等待条件变量,这样如果调用花费的时间比超时时间长,你仍然会醒来,知道它,并能够抛出异常——所有这些都在主线程中。这是代码(现场演示here):

#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>

using namespace std::chrono_literals;

int f()
{
    std::this_thread::sleep_for(10s); //change value here to less than 1 second to see Success
    return 1;
}

int f_wrapper()
{
    std::mutex m;
    std::condition_variable cv;
    int retValue;

    std::thread t([&cv, &retValue]() 
    {
        retValue = f();
        cv.notify_one();
    });

    t.detach();

    {
        std::unique_lock<std::mutex> l(m);
        if(cv.wait_for(l, 1s) == std::cv_status::timeout) 
            throw std::runtime_error("Timeout");
    }

    return retValue;    
}

int main()
{
    bool timedout = false;
    try {
        f_wrapper();
    }
    catch(std::runtime_error& e) {
        std::cout << e.what() << std::endl;
        timedout = true;
    }

    if(!timedout)
        std::cout << "Success" << std::endl;

    return 0;
}

【讨论】:

  • 不会阻止 f() 运行。如果在f() 从第一次尝试退出之前再次运行,f() 线程安全吗?看起来这可能会带来更多问题。
  • OP 没有说明停止 f 是一项要求。重入/线程安全问题可以通过这种方法通过进一步阐述来解决——这里的内容足以证明本质。
  • 为什么要在 lambda 中捕获m
  • 不知道。编辑删除所述捕获
【解决方案2】:

您也可以使用std::packaged_task 在另一个线程中运行您的函数 f()。这个解决方案或多或少类似于this 之一,只是它使用标准类来包装。

std::packaged_task<void()> task(f);
auto future = task.get_future();
std::thread thr(std::move(task));
if (future.wait_for(1s) != std::future_status::timeout)
{
   thr.join();
   future.get(); // this will propagate exception from f() if any
}
else
{
   thr.detach(); // we leave the thread still running
   throw std::runtime_error("Timeout");
}

您甚至可以尝试将其包装到函数模板中,以允许调用具有超时的任意函数。大致如下:

template <typename TF, typename TDuration, class... TArgs>
std::result_of_t<TF&&(TArgs&&...)> run_with_timeout(TF&& f, TDuration timeout, TArgs&&... args)
{
    using R = std::result_of_t<TF&&(TArgs&&...)>;
    std::packaged_task<R(TArgs...)> task(f);
    auto future = task.get_future();
    std::thread thr(std::move(task), std::forward<TArgs>(args)...);
    if (future.wait_for(timeout) != std::future_status::timeout)
    {
       thr.join();
       return future.get(); // this will propagate exception from f() if any
    }
    else
    {
       thr.detach(); // we leave the thread still running
       throw std::runtime_error("Timeout");
    }
}

然后使用:

void f1() { ... }
call_with_timeout(f1, 5s);

void f2(int) { ... }
call_with_timeout(f2, 5s, 42);

int f3() { ... }
int result = call_with_timeout(f3, 5s);

这是一个在线示例:http://cpp.sh/7jthw

【讨论】:

  • 为什么不用 std::async 代替 packaged_task,让它为你处理线程
  • 虽然 std::async 为您处理线程,但在超时的情况下无法与线程分离。调用者代码仍将等待 f() 在 future 的销毁点完成。通常这意味着包装函数或代码范围将与 f() 本身一样长,这对我们的目标没有意义。有ways to overcome this,但是明确地使用线程可能更容易。
【解决方案3】:

可以新建一个线程,异步等待1s过去,然后抛出异常。但是,异常只能在抛出它们的同一个线程中捕获,因此,您不能在调用 f() 的同一个线程中捕获,就像在您的示例代码中一样 - 但这不是规定的要求,所以它可能是没问题。

只有保证f在1s内返回,才可以同步进行:

  • 存储当前时间
  • 致电f()
  • 等待当前时间 - 存储时间 + 1s

但要证明f 确实及时返回可能相当困难。

【讨论】:

    【解决方案4】:

    这建立在 Smeehee 的示例之上,如果您需要一个接受可变数量参数的示例(另请参阅 https://github.com/goblinhack/c-plus-plus-examples/blob/master/std_thread_timeout_template/README.md

    #include <condition_variable>
    #include <iostream>
    #include <mutex>
    #include <thread>
    
    int my_function_that_might_block(int x)
    {
        std::this_thread::sleep_for(std::chrono::seconds(10));
        return 1;
    }
    
    template<typename ret, typename T, typename... Rest>
    using fn = std::function<ret(T, Rest...)>;
    
    template<typename ret, typename T, typename... Rest>
    ret wrap_my_slow_function(fn<ret, T, Rest...> f, T t, Rest... rest)
    {
        std::mutex my_mutex;
        std::condition_variable my_condition_var;
        ret result = 0;
    
        std::unique_lock<std::mutex> my_lock(my_mutex);
    
        //
        // Spawn a thread to call my_function_that_might_block(). 
        // Pass in the condition variables and result by reference.
        //
        std::thread my_thread([&]() 
        {
            result = f(t, rest...);
            // Unblocks one of the threads currently waiting for this condition.
            my_condition_var.notify_one();
        });
    
        //
        // Detaches the thread represented by the object from the calling 
        // thread, allowing them to execute independently from each other. B
        //
        my_thread.detach();
    
        if (my_condition_var.wait_for(my_lock, std::chrono::seconds(1)) == 
                std::cv_status::timeout)  {
            //
            // Throw an exception so the caller knows we failed
            //
            throw std::runtime_error("Timeout");
        }
    
        return result;    
    }
    
    int main()
    {
        // Run a function that might block
    
        try {
            auto f1 = fn<int,int>(my_function_that_might_block);
            wrap_my_slow_function(f1, 42);
            //
            // Success, no timeout
            //
        } catch (std::runtime_error& e) {
            //
            // Do whatever you need here upon timeout failure
            //
            return 1;
        }
    
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-05-23
      • 1970-01-01
      • 2011-02-24
      • 2010-10-27
      • 2012-04-26
      • 2011-06-28
      • 2021-01-21
      相关资源
      最近更新 更多