调用超时方法

时间:2019-07-04 08:51:34

标签: c++ c++14 c++17

我要调用的方法foo()超时(例如1分钟)。如果执行时间不到1分钟,则返回结果。否则将引发异常。这是代码:

//PRINT "START" IN THE LOG
auto m = std::make_shared<std::mutex>();
auto cv = std::make_shared<std::condition_variable>();
auto ready = std::make_shared<bool>(false);
auto response = std::make_shared<TResponse>();
auto exception = std::make_shared<FooException>();
exception->Code = ErrorCode::None;

std::thread([=]
{
    std::unique_lock<std::mutex> lk(*m);
    cv->wait(lk, [=]{ return *ready; });

    try
    {
        //PRINT "PROCESS" IN THE LOG
        auto r = foo();
        *response = std::move(r);
    }
    catch(const FooException& e)
    {
        *exception = std::move(e);
    }

    lk.unlock();
    cv->notify_one();
}).detach();

std::unique_lock<std::mutex> lk(*m);
*ready = true;
cv->notify_one();
auto status = cv->wait_for(lk, std::chrono::seconds(60));
if (status == std::cv_status::timeout)
{
    //PRINT "TIMEOUT" IN THE LOG
    //throw timeout exception
}
else
{
    //PRINT "FINISH" IN THE LOG
    if (exception->Code == ErrorCode::None)
    {
        return *response;
    }
    else
    {
        throw *exception;
    }
}

您可以看到我在代码中添加了日志START / PROCESS / FINISH / TIMEOUT,每次执行此方法时,我都可以在日志中看到START / PROCESS / FINISH或START / PROCESS / TIMEOUT模式。但是,有时日志是START / PROCESS,没有任何完成/超时。我认为cv->wait_for最多应将当前线程阻塞60秒,然后它以TIMEOUT或FINISH存在。

foo()方法包含对网络驱动器的磁盘IO操作,这些操作有时会挂起1小时以上(原因与该问题无关,现在无法解决),我尝试替换{ {1}}处于线程睡眠状态,一切正常。这段代码有什么问题,我该如何改进?

4 个答案:

答案 0 :(得分:4)

由于在cv->wait_for调用中没有谓词,因此线程可能会被虚假地解除阻塞。但是,奇怪的是没有打印完成/超时。因此,我们可能在这里需要更多信息:该程序会发生什么?它是否挂起,是否抛出,是否刚刚退出,是否在cv->wait_for之后的行中打印?

您可以尝试使用std::async,看看是否出现相同的行为(此外,它将大大简化您的代码):

std::future<int> res = std::async(foo);

std::future_status stat = res.wait_for(std::chrono::seconds(60));

if (stat != std::future_status::ready) {
  std::cout << "Timed out..." << "\n";
} else {
  try {
    int result = res.get();
    std::cout << "Result = " << result << std::endl;
  } catch (const FooException& e) {
    std::cerr << e.what() << '\n';
  }
}

编辑CuriouslyRecurringThoughts的注释所指出,析构函数中std::async块的未来。如果这不是一个选择,那么下面的代码将使用std::promise和一个分离线程:

std::promise<int> prom;
std::future<int> res = prom.get_future();

std::thread([p = std::move(prom)]() mutable {
  try {
    p.set_value(foo());
  } catch (const std::exception& e) {
    p.set_exception(std::current_exception());
  }
}).detach();

等待std::future的操作如前所示。

答案 1 :(得分:4)

看来,尽管等待时间到了,但主线程仍然死锁,因为即使cv->wait_for超时返回时,它仍然会尝试lk.lock()锁定当前由第二个线程锁定的互斥体。

cppreference中关于wait_for所述:

  

无论出于何种原因,当解除阻止时,都会重新获得锁定,并且wait_for()退出。

我不确定为什么promise / future解决方案对您不起作用,因为您没有在此处发布该示例,但是我尝试了一个简单的版本,即使第二个线程“挂起”:

using namespace std::chrono_literals;

std::cout << "START" << std::endl;
std::promise<void> p;
auto f = p.get_future();
std::thread t([p = std::move(p)]() mutable {
    std::cout << "PROCESS" << std::endl;
    std::this_thread::sleep_for(5min);
    p.set_value();
});

auto status = f.wait_for(5s);
std::cout << (status == std::future_status::ready ? "FINISH" : "TIMEOUT") << std::endl;
t.join();

输出符合预期:

START
PROCESS
TIMEOUT

答案 2 :(得分:1)

我们可以创建一个单独的线程来运行调用本身,然后在您的主线程中等待条件变量,一旦返回foo,该线程就会对该线程发出信号。

诀窍是等待条件变量达到60s超时,这样,如果调用花费的时间超过超时时间,您仍将醒来,了解它,并能够引发异常-全部在主线程中

请在下面找到代码示例:

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

using namespace std::chrono_literals;

int foo()
{

    //std::this_thread::sleep_for(10s); //Will Return  Success
    std::this_thread::sleep_for(70s); //Will Return  Timeout
    return 1;
}

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

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

    t.detach();

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

    return retValue;    
}

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

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

    return 0;
}

如果我们在std::this_thread::sleep_for(10s);中使用foo,则会返回SUCCESS 而且,如果我们在std::this_thread::sleep_for(70s);中使用foo,则会返回TIMEOUT

希望对您有帮助!

答案 3 :(得分:0)

正如Mike van Dyke所说,documentation很清楚,您需要一个谓词来正确使用条件变量来处理虚假唤醒:

  

当通知条件变量,超时到期或发生虚假唤醒时,将唤醒线程,并自动重新获取互斥量。 然后线程应检查条件并在唤醒是虚假的情况下继续等待。

任何使用condvar来等待而没有循环和谓词都是错误的。它应该始终具有明确的while(!predicate)循环或类似如下的内容:

std::unique_lock<std::mutex> lk(*m);
auto status = cv->wait_for(lk, std::chrono::seconds(60), predicate);
if (status == std::cv_status::timeout)
{ /*...*/ } else { /*...*/ }

这意味着您需要一些谓词来检查:在通知线程中的condvar之前设置*ready = false(并使用!*ready作为谓词)就可以了。

至于为什么您看不到预期的结果-我不知道,因为我看不到您的实际日志记录代码或您提供的代码段之外发生了什么。从wait_for醒来而没有超时或未收到有效的响应或异常是最有可能的,但是您将不得不调试代码或提供完整的示例来帮助解决问题。