限制函数的执行时间C++

198 阅读4分钟

总结

  • 在lamada中,如果超时,引用这局部变量,如condition, 会崩溃, 解决的方案:使用智能指针
  • 控制输出格式需要使用iomanip, manipulate 是操作的含义
  • 线程释放的时候,要检查释放joinable, 如果是joinable,并且现场没有结束,会触发崩溃。

限制函数执行时间的示例

#include <iostream>
#include <string>
#include <memory>
#include <thread>
#include <sys/time.h>
#include <iomanip>

std::string time_to_string()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    
    
    uint64_t second = tv.tv_sec;
    int millisecond = tv.tv_usec/1000;
    struct tm *tm_ptr;
    
    // 转换为本地时间
    tm_ptr = localtime((const time_t*)&second);
    char time_str[30]{0};
    strftime(time_str, 30, "%Y-%m-%d %H:%M:%S", tm_ptr);
    snprintf(time_str, 29, "%s.%.03d", time_str,millisecond);
    std::string result(time_str);
    return result;
}

void max_run_time(int time)
{
    std::shared_ptr<std::string> res = std::make_shared<std::string>();
    std::shared_ptr<std::condition_variable> condition = std::make_shared<std::condition_variable>();
    
    //模拟超时任务
    std::thread t([time, res, condition]() {
        std::cout << std::left << std::setw(12) << "[lamada]" << time_to_string() << " res.use_cout: " << res.use_count() << std::endl;
        std::cout << std::left << std::setw(12) << "[lamada]" << time_to_string() << " res.get: " << res.get() << std::endl;
        std::cout << std::left << std::setw(12) << "[lamada]" << time_to_string() << " condition.use_cout: " << condition.use_count() << std::endl;
        std::cout << std::left << std::setw(12) << "[lamada]" << time_to_string() << " condition.get: " << condition.get()<< std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(time));
        std::cout << std::left << std::setw(12) << "[lamada]" << time_to_string() << " end" << std::endl;
        condition->notify_one();
    });
    t.detach();

    std::mutex lock;
    std::unique_lock<std::mutex> locker(lock);
    auto cv = condition->wait_for(locker, std::chrono::seconds(2));
    if(cv == std::cv_status::timeout)
    {
        std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " 超时的情况,预期引用为2, lamada没有执行完,当前函数没有执行完,所以是2。"<< std::endl;
        std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " res.use_cout: " << res.use_count() << std::endl;
        std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " res.get: " << res.get() << std::endl;
        std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " condition.use_cout: " << condition.use_count() << std::endl;
        std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " condition.get: " << condition.get() << std::endl;
        return ;
    }
    
    std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << "正常的情况,预期引用为1" << std::endl;
    std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " res.use_cout: " << res.use_count() << std::endl;
    std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " res.get: " << res.get() << std::endl;
    std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " condition.use_cout: " << condition.use_count() << std::endl;
    std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " condition.get: " << condition.get() << std::endl;
    return ;
}

int main(int argc, const char * argv[])
{
    std::cout << "模拟正常情况:" << std::endl;
    max_run_time(1);
    std::cout << std::endl;
    std::cout << "模拟超时情况:" << std::endl;
    max_run_time(3);
    return 0;
}

detach

编写测试代码的时候遇到了一个问题,没有调用detach,程序崩溃,崩溃的堆栈:

thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
frame #0: 0x00007ff8070b8202 libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x00000001000745c2 libsystem_pthread.dylib`pthread_kill + 263
frame #2: 0x00007ff807016b45 libsystem_c.dylib`abort + 123
frame #3: 0x00007ff8070aa282 libc++abi.dylib`abort_message + 241
frame #4: 0x00007ff80709c31f libc++abi.dylib`demangling_terminate_handler() + 47
frame #5: 0x00007ff8070a96db libc++abi.dylib`std::__terminate(void (*)()) + 6
frame #6: 0x00007ff8070a9680 libc++abi.dylib`std::terminate() + 32
frame #7: 0x00007ff8070394cd libc++.1.dylib`std::__1::thread::~thread() + 17
frame #8: 0x0000000100002b21 timeout`test_thread(time=10) at main.cpp:85:1

原因(www.zhihu.com/question/46…

参见《effective modern c++》 item 37,make thread unjoinable in all path. 在std::thread的析构函数中,有一句断言 assert(!joinable());不满足的情况下,程序会crash。

本质上是因为std::thread不是完全RAII的类,它管理的系统线程要用户手动去释放(join, detach或者move到另一个std::thread)。当一个std::thread析构时,它不应该还在管理一个正在运行的系统线程, 它通过断言来强制这一点,保证用户正确地使用这个库。之所以std::thread没有在析构函数的释放系统线程的机制,是因为join(等待线程执行完毕)和detach(让线程解绑独立执行)都有缺点,join可能造成死锁,detach可能有dangling reference。多线程编程本来就容易出bug,强迫开发者思考这个问题可能是更好的选择。

当然你可以自己封装一个RAII的线程

class RAIIThread: public std::thread {
...
  ~RAIIThread() {
    if (t.joinable()) t.join();
  }
private:
  std::thread t;
};

除非因为某些特殊需要(固定的线程数量或者性能很重要的组件)只能自己管理线程池或者事件循环,这时候只能用std::thread或pthread。大多数情况下,std::async是更好的选择,标准库往往比你更好地管理线程数量和均衡负载,同时是是完全RAII的,有更好的异常安全性。