C++保证函数能在超时时间内返回

Table of Contents

    使用这两个接口就可以了

    功能:保证某个操作能在超时时间内返回

    说明:shared_from_this保证在退出作用域的时候智能指针不被释放,在线程退出的时候再自动销毁。

    每执行一次execTimeout都会启动一个新线程,在线程退出后自动回收所有资源,注意T类型需要能被拷贝赋值

    解决问题:某些操作会导致当前线程被阻塞(特别是网络操作),而这些接口又是第三方库提供的我们没办法修改

    不适用:操作非常频繁的接口;可能会被永久阻塞的接口

    demo:

     // 执行正常返回
    int result1 = execTimeout<int>(3000, []() -> int {
        return 1 + 2;
    });
    std::cout << "result1:" << result1 << std::endl;
    
    // 执行超时(执行函数需要花费4s而超时时间是3s),返回默认值
    int result2 = execTimeout<int>(3000, 0, []() -> int {
        std::this_thread::sleep_for(std::chrono::seconds(4));
        return 3 + 4;
    });
    std::cout << "result2:" << result2 << std::endl;
    
    #pragma once
    
    #include <condition_variable>
    #include <mutex>
    #include <memory>
    #include <thread>
    
    template<typename T>
    T execTimeout(int msTimeout, const std::function<T()> &cb)
    {
        std::shared_ptr<TimeoutGuard<T>> a(new TimeoutGuard<T>(msTimeout));
        return a->exec(cb);
    }
    
    template<typename T>
    T execTimeout(int msTimeout, const T &defaultValue, const std::function<T()> &cb)
    {
        std::shared_ptr<TimeoutGuard<T>> a(new TimeoutGuard<T>(msTimeout, defaultValue));
        return a->exec(cb);
    }
    
    //////////////////////////////////////////////////////////////////////////
    template<typename T>
    class TimeoutGuard : public std::enable_shared_from_this<TimeoutGuard<T>>
    {
    public:
        TimeoutGuard(int msTimeout)
            : msTimeout_(msTimeout), isOk_(false)
        {
        }
    
        TimeoutGuard(int msTimeout, const T &defaultValue)
            : msTimeout_(msTimeout), result_(defaultValue), isOk_(false)
        {
        }
    
        TimeoutGuard(const TimeoutGuard&) = delete;
        TimeoutGuard& operator=(const TimeoutGuard&) = delete;
    
        T exec(const std::function<T()> &cb)
        {
            if (s_threadCount > 300) {
                std::cerr << "[TimeoutGuard] thread count overload:" << s_threadCount << std::endl;
                return result_;
            }
    
            cb_ = cb;
            long long startTime = getCurrentMillisecond();
            auto self(shared_from_this());
            thread_.reset(new std::thread([self]() {
                ++s_threadCount;
                std::cout << "[TimeoutGuard] thread start id:" << std::this_thread::get_id() << ", count:" << s_threadCount << std::endl;;
                T t = self->cb_();
                std::unique_lock<std::mutex> lock(self->mutex_);
                self->isOk_ = true;
                self->result_ = t;
                self->cond_.notify_one();
                --s_threadCount;
                std::cout << "[TimeoutGuard] thread end id:" << std::this_thread::get_id() << ", count:" << s_threadCount << std::endl;
            }));
            thread_->detach();
    
            std::unique_lock<std::mutex> lock(mutex_);
            while (!isOk_ && startTime + msTimeout_ > getCurrentMillisecond()) {
                cond_.wait_for(lock, std::chrono::milliseconds(100));
            }
            return result_;
        }
    
        long long getCurrentMillisecond()
        {
            auto duration = std::chrono::system_clock::now().time_since_epoch();
            auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
            return millis;
        }
    
    private:
        std::mutex mutex_;
        std::condition_variable cond_;
        std::unique_ptr<std::thread> thread_;
        std::function<T()> cb_;
        T result_;
        bool isOk_;
        long long msTimeout_;
        static int s_threadCount;
    };
    
    template<typename T>
    int TimeoutGuard<T>::s_threadCount = 0;