build_embed_linux_system

时间time处理接口

在Linux系统中,时间处理是非常重要的,因为许多应用程序需要精确的时间戳来记录事件、进行调度和同步。Linux提供了一系列的时间相关处理函数,这些函数主要位于头文件中。

delay_timer

延时函数是Linux中比较常用的一类函数,主要包含sleep,usleep,nanosleep函数。

// 相关头文件
#include <unistd.h>

// 用于暂停程序执行指定的秒数
// @second 要暂停的秒数
// @return 如果成功,返回0;如果被信号中断,返回剩余的秒数
unsigned int sleep(unsigned int seconds)

// 使当前进程暂停执行指定的微秒
// @usec 是要暂停的微秒数
// @return 如果成功,返回0;如果被信号中断,返回剩余的秒数
int usleep(useconds_t usec);

struct timespec {
    time_t tv_sec;        /* 秒 */
    long   tv_nsec;       /* 纳秒 */
};
// 使当前进程暂停执行指定的纳秒数
// req:指向timespec结构的指针,表示要暂停的时间。
// rem:指向timespec结构的指针,用于存储剩余的时间
// 如果成功,返回0。如果被信号中断,返回-1,并设置errno为EINTR。
int nanosleep(const struct timespec *req, struct timespec *rem);

cv_timer

std::condition_variable是同步原语,用于线程间的通讯,它通常与std::mutex和std::unique_lock一起使用。其可以实现超时等待的功能执行,此对象的操作接口如下所示。

// 相关头文件
#include <unistd.h>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>

//函数用于阻塞当前线程,直到条件变量被通知。
//lock: 一个 std::unique_lock<std::mutex> 对象,用于锁定互斥量
//pred:一个可调用对象(如函数、函数指针、lambda 表达式等),用于检查等待的条件是否满足
void wait(std::unique_lock<std::mutex>& lock);
template<class Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred);

//用于阻塞当前线程,直到条件变量被通知或指定的时间间隔过去
//lock:用于锁定互斥量
//real_time:一个 std::chrono::duration 对象,表示等待的时间间隔 
//pred:一个可调用对象(如函数、函数指针、lambda 表达式等),用于检查等待的条件是否满足 
template<class Rep, class Period>
std::cv_status wait_for(std::unique_lock<std::mutex>& lock,
                        const std::chrono::duration<Rep, Period>& rel_time);
template<class Rep, class Period, class Predicate>
bool wait_for(std::unique_lock<std::mutex>& lock,
              const std::chrono::duration<Rep, Period>& rel_time,
              Predicate pred);

//函数用于阻塞当前线程,直到条件变量被通知或到达指定的时间点
//lock:用于锁定互斥量
//abs_time:一个std::chrono::time_point对象,表示等待的截止时间点
//pred:一个可调用对象(如函数、函数指针、lambda 表达式等),用于检查等待的条件是否满足 
template<class Clock, class Duration>
std::cv_status wait_until(std::unique_lock<std::mutex>& lock,
                          const std::chrono::time_point<Clock, Duration>& abs_time);
template<class Clock, class Duration, class Predicate>
bool wait_until(std::unique_lock<std::mutex>& lock,
                const std::chrono::time_point<Clock, Duration>& abs_time,
                Predicate pred);

// 函数用于唤醒一个正在等待此条件变量的线程
void notify_one() noexcept;

// 函数用于唤醒所有正在等待此条件变量的线程。
void notify_all() noexcept;

基于上述接口,配合std::thread, 可以实现如下超时接口。

#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <functional>
#include <unistd.h>

class TIME_MANAGE_CV
{
public:
    TIME_MANAGE_CV() = default;
    TIME_MANAGE_CV(const TIME_MANAGE_CV& timer) = delete;

    ~TIME_MANAGE_CV() {
        stop();
    }

    /// \brief start
    /// 启动线程运行,实现定时器功能
    /// \param interval - 定时器执行的间隔时间
    /// \param task - 定时器执行的回调函数
    void start(int interval, std::function<void()> task)
    {
        if (is_running_)
            return;

        millisecond_ = interval;
        timeout_handler_ = task;
        is_running_ = true;

        //start thread
        std::thread(std::bind(&TIME_MANAGE_CV::run, this)).detach();
    }

    /// @brief stop
    /// 运行停止函数,结束循环执行
    void stop() {
        if(!is_running_) {
            return;
        }

        is_running_ = false;
        cond_.notify_all();
    }

private:
    /// \brief run
    /// 线程执行函数,该函数在独立线程中执行,用于实现超时循环
    void run() {
        while(is_running_) {
            std::unique_lock<std::mutex> lock{mutex_};
            auto stauts = cond_.wait_for(lock, std::chrono::milliseconds(millisecond_));
            if (stauts == std::cv_status::timeout) {
                if (timeout_handler_) {
                    timeout_handler_();
                }
            } else {
                break;
            }
        }
        std::cout<<"thread finished!\n";
    }

private:
    /// \brief is_running_
    /// - 判断线程释放允许
    std::atomic<bool> is_running_ = false;

    /// \brief mutex_
    /// - mutex_ used for condition.
    std::mutex mutex_;

    /// \brief cond_
    /// - 线程通讯变量
    std::condition_variable cond_;

    /// \brief timeout_handler_
    /// - 超时执行函数
    std::function<void()> timeout_handler_;

    /// \brief millisecond_
    /// - 等待的毫秒超时时间
    uint32_t millisecond_{1};
};

int main(int argc, char* argv[])
{
    // 创建定时器对象
    TIME_MANAGE_CV mcv;
    int index = 0;

    // 实现定时器和回调函数
    mcv.start(1000, [&](){
        std::cout<<"cv timer loop: "<<index++<<std::endl;
    });

    while(1) {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        if (index == 5) {
            mcv.stop();
            index++;
        } else if (index > 5) {                  
            index = 0;
            mcv.start(1000, [&](){
                std::cout<<"cv new timer loop: "<<index++<<std::endl;
            });
        }
    }
    return 0;
}

std::condition_variable 的 wait_for 和 wait_until 方法依赖于系统时间来判断是否超时。如果在这些方法调用期间修改了系统时间,可能会导致以下情况:

  1. 提前唤醒:如果将系统时间向前调整,使得当前时间超过了 wait_until 指定的时间点,那么等待的线程可能会被提前唤醒,即使条件变量尚未被通知。
  2. 延迟唤醒:如果将系统时间向后调整,使得当前时间还未达到 wait_until 指定的时间点,那么等待的线程可能会被延迟唤醒,即使条件变量已经被通知。
  3. 死锁:如果系统时间的修改导致线程永远无法达到 wait_until 指定的时间点,那么线程可能会永远等待下去,从而导致死锁。

而修改系统时间在嵌入式Linux系统中是很可能发生的事,例如NTP时间同步,RTC时间同步,所以使用这个接口目前来看是不可靠的,可以用来了解和属性事件同步,以及定时服务的方法,不建议用于实际产品中。改进方案有使用std::condition_variable_any配合std::chrono::steady_clock不依赖系统时钟(见例程),或者使用posix的超时接口(参考下节)。

class CONDITION_ANY
{
public:
    void test(void) {
        std::thread task(std::bind(&CONDITION_ANY::run, this));

        std::unique_lock<std::mutex> lock(mutex_);
        if (cond_.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [&]{ return ready_; })) {
            std::cout << "Singal action." << std::endl;
        } else {
            std::cout << "Timeout action." << std::endl;
        }
        task.join();
    }

private:
    void run() {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        {
            std::lock_guard<std::mutex> lock(mutex_);
            ready_ = true;
        }
        cond_.notify_one(); 
    }

private:
    std::mutex mutex_;
    std::condition_variable_any cond_;
    bool ready_{false};
};

posix_timer

POSIX线程库中也支持线程的条件等待,并允许设置一个超时时间。主要接口包含pthread_mutex_init,pthread_mutex_lock, pthread_mutex_unlock,pthread_condattr_init,pthread_condattr_setclock,pthread_cond_init,pthread_cond_timedwait,pthread_cond_signal,pthread_cond_broadcast,相关操作接口如下所示。

// 相关头文件
#include <pthread.h>

// 初始化一个互斥锁
// @mutex 指向要初始化的互斥锁的指针
// @attr 指向互斥锁属性的指针,通常为NULL,表示使用默认属性。
// @return 成功返回0,失败返回错误码 
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

// 锁定一个互斥锁。如果互斥锁已经被锁定,调用线程将阻塞,直到互斥锁被解锁。
// @mutex 指向要锁定的互斥锁的指针
// @return 成功返回0,失败返回错误码。
int pthread_mutex_lock(pthread_mutex_t *mutex);

// 解锁一个互斥锁
// @mutex 指向要解锁的互斥锁的指针
// @return 成功返回0,失败返回错误码。
int pthread_mutex_unlock(pthread_mutex_t *mutex);

// 初始化一个条件变量属性对象
// @attr 指向要初始化的条件变量属性对象的指针
// @return 成功返回0,失败返回错误码
int pthread_condattr_init(pthread_condattr_t *attr);

// 设置条件变量属性对象的时钟类型
// @attr 指向要设置的条件变量属性对象的指针
// @clock_id 指定的时钟类型,如CLOCK_REALTIME或CLOCK_MONOTONIC
// @return 成功返回0,失败返回错误码
int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id);

// 初始化一个条件变量。
// @cond 指向要初始化的条件变量的指针
// @attr 指向条件变量属性的指针,通常为NULL,表示使用默认属性
// @return 成功返回0,失败返回错误码
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

// 用于线程的条件等待,并允许设置一个超时时间
// @cond:指向一个 pthread_cond_t 类型的条件变量。
// @mutex:指向一个 pthread_mutex_t 类型的互斥锁。
// @abstime:指向一个 struct timespec 类型的结构体,表示绝对时间,即等待的截止时间。
// @return 成功返回0,超时返回ETIMEOUT,被信号中断,返回EINTR
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
                           pthread_mutex_t *restrict mutex,
                           const struct timespec *restrict abstime);

// 唤醒一个等待在条件变量上的线程。
// @cond 指向要唤醒的条件变量的指针
// @return 成功返回0,失败返回错误码
int pthread_cond_signal(pthread_cond_t *cond);

// 唤醒所有等待在条件变量上的线程
// @cond 指向要唤醒的条件变量的指针
// @return 成功返回0,失败返回错误码
int pthread_cond_broadcast(pthread_cond_t *cond);

基于上述接口,配合std::thread, 可以实现如下超时接口。

#include <thread>
#include <iostream>
#include <pthread.h>
#include <atomic>
#include <functional>

class TIME_MANAGE_POSIX
{
public:
    /// \brief constructor
    TIME_MANAGE_POSIX() = default;
    TIME_MANAGE_POSIX(const TIME_MANAGE_POSIX &time) = delete;

    /// \brief destructor
    ~TIME_MANAGE_POSIX(){
        stop();

        pthread_cond_destroy(&m_cond_);
        pthread_mutex_destroy(&m_mutex_);
    }

    /// \brief start
    /// - This method is used to start TIME_MANAGE_POSIX thread
    /// \param interval - time for the thread run period
    /// \param task - task when the thread run.
    void start(int interval, std::function<void()> task)
    {
        pthread_condattr_t attr;

        millisecond_ = interval;
        timeout_handler_ = task;

        pthread_mutex_init(&m_mutex_, NULL);
        pthread_condattr_init(&attr);
        pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
        pthread_cond_init(&m_cond_, &attr); 

        //start thread
        std::thread(std::bind(&TIME_MANAGE_POSIX::run, this)).detach();
    }

    /// \brief stop
    /// - This method is used to stop one TIME_MANAGE_POSIX thread
    void stop()
    {
        if (stop_loop_)
            return;
        
        stop_loop_ = true;
        pthread_mutex_lock(&m_mutex_);
        pthread_cond_broadcast(&m_cond_);
        pthread_mutex_unlock(&m_mutex_);
    }

private:
    /// \brief run
    /// - This method is used to run the koop by thread
    void run()
    {
        struct timespec ts;
        uint64_t millisecond;

        stop_loop_ = false;

        while (!stop_loop_)
        {
            clock_gettime(CLOCK_MONOTONIC, &ts);
            millisecond = (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000) + millisecond_;
            ts.tv_sec = millisecond / 1000;
            ts.tv_nsec = (millisecond - ((millisecond / 1000) * 1000)) * 1000 * 1000;

            pthread_mutex_lock(&m_mutex_);
            auto stauts = pthread_cond_timedwait(&m_cond_, &m_mutex_, &ts);
            pthread_mutex_unlock(&m_mutex_);

            if (stauts == ETIMEDOUT)
            {
                if (timeout_handler_)
                {
                    timeout_handler_();
                }
            }
            else
            {
                std::cout<<"TIME_MANAGE_POSIX finished\n";
            }
        }
    }

private:
    /// \brief m_cond_
    /// - cond used to wait timeout.
    pthread_cond_t m_cond_;

    /// \brief m_mutex
    /// - mutex protect
    pthread_mutex_t m_mutex_;

    /// \brief millisecond_
    /// - ms wait for timeout.
    uint64_t millisecond_{1};

    /// \brief timeout_handler_
    /// - function run when timeout.
    std::function<void()> timeout_handler_;

    /// \brief stop_loop_
    /// - loop flag until stop.
    std::atomic<bool> stop_loop_{false};
};

int main(int argc, char* argv[])
{
    int index = 0;
    TIME_MANAGE_POSIX tmc;

    tmc.start(1000, [&](){
        std::cout<<"posix timer loop: "<<index++<<std::endl;
    });

    while(1) {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        if (index == 5) {
            tmc.stop();
            index++;
        } else if (index > 5) {                  
            index = 0;
            tmc.start(1000, [&](){
                std::cout<<"posix new timer loop: "<<index++<<std::endl;
            });
        }
    }
    return 0;
}

next_chapter

返回目录

直接开始下一节说明: 网络开发调试接口