基于C++标准库实现定时器类

news2024/12/24 8:47:25

基于C++标准库实现定时器类

定时器类是多线程编程中经常设计到的工具类
简单的定时器原理其实很简单(是不是有点GNU is not unix的味道;):

  • 创建一个新线程
  • 在那个线程里等待
  • 等待指定时长后做任务

python标准库中就有这么一个定时器类:threading.Timer
此类表示一个操作应该在等待一定的时间之后运行 --- 相当于一个定时器。
Timer 类是 Thread 类的子类,因此可以像一个自定义线程一样工作。
与线程一样,通过调用 start() 方法启动定时器。 而 cancel() 方法可以停止计时器(在计时结束前)。

例如:

def hello():
    print("hello, world")

t = Timer(30.0, hello)
t.start()  # after 30 seconds, "hello, world" will be printed

class threading.Timer(interval, function, args=None, kwargs=None) 创建一个定时器,在经过 interval 秒的间隔事件后,将会用参数 args 和关键字参数 kwargs 调用 function。
如果 args 为 None (默认值),则会使用一个空列表。如果 kwargs 为 None (默认值),则会使用一个空字典。

start() 启动定时器。

cancel() 停止定时器并取消执行计时器将要执行的操作。仅当计时器仍处于等待状态时有效。

接下来我将给出两种C++的Timer实现,接口类似于python的threading.Timer,不过精度为秒级的,其原因有二:

  • 实现代码参考了Posix多线程编程里的alarm实例程序,为了方便大家对比C语言版本的实现,这里也以秒为单位
  • 避免一上来过多的涉及C++标准库中<chrono>的用法,使代码逻辑更清晰的集中在定时器相关的部分

当然,作为一个在产品级代码中可用的Timer,精度至少应该在毫秒级才行,所以文章最后也会给出精度在微秒的代码实现的链接。

首先,给出C++版本的Timer的接口定义:
几乎完全仿照python的threading.Timer,

class Timer {
public:
    typedef std::function<void ()> Callback;

    Timer(int interval, Callback function); 
    void start();
    void cancel(); 
};
  • Callback:类型为std::function<void ()>,即返回类型为void的“函数”,当然在C++里可以是普通函数,函数对象,lambda等。
  • Timer(interval, function):构造函数,创建一个Timer,interval秒后到期(相对于调用start函数时的时间点),回调函数为function。
  • start():启动定时器。
  • cancel():停止定时器并取消执行计时器将要执行的操作。

在给出C++的实现前,我们先给出测试驱动程序。测试驱动程序来源于《Posix多线程程序设计》(英文原版书名为Programming with POSIX Threads)里的闹钟实例程序。
而我接下来的介绍顺序源于我的编码实现顺序,如下:

  • 先给出书中基于pthread的多线程版本的实例代码,C代码
  • 将C版本的代码转化成等价的python代码,基于threading.Timer接口实现的版本
  • 将python版本的代码,转化成C++版本,并基于C++的Timer类接口,得到C++的Timer类的测试驱动代码
  • 实现C++版的Timer类,并且编译测试驱动代码,运行验证

那么,我先给出基于pthread的多线程版本的实例代码,C代码:

/*
 * alarm_fork.c
 *
 * This version of alarm.c uses pthread_create to create a
 * separate thread to wait for each alarm to expire.
 */
#include <pthread.h>
#include "errors.h"

typedef struct alarm_tag {
    int         seconds;
    char        message[64];
} alarm_t;

void *alarm_thread (void *arg)
{
    alarm_t *alarm = (alarm_t*)arg;
    int status;

    status = pthread_detach (pthread_self ());
    if (status != 0)
        err_abort (status, "Detach thread");
    sleep (alarm->seconds);
    printf ("(%d) %s\n", alarm->seconds, alarm->message);
    free (alarm);
    return NULL;
}

int main (int argc, char *argv[])
{
    int status;
    char line[128];
    alarm_t *alarm;
    pthread_t thread;

    while (1) {
        printf ("Alarm> ");
        if (fgets (line, sizeof (line), stdin) == NULL) exit (0);
        if (strlen (line) <= 1) continue;
        alarm = (alarm_t*)malloc (sizeof (alarm_t));
        if (alarm == NULL)
            errno_abort ("Allocate alarm");

        /*
         * Parse input line into seconds (%d) and a message
         * (%64[^\n]), consisting of up to 64 characters
         * separated from the seconds by whitespace.
         */
        if (sscanf (line, "%d %64[^\n]", 
            &alarm->seconds, alarm->message) < 2) {
            fprintf (stderr, "Bad command\n");
            free (alarm);
        } else {
            status = pthread_create (
                &thread, NULL, alarm_thread, alarm);
            if (status != 0)
                err_abort (status, "Create alarm thread");
        }
    }
}

代码的完整说明参见《Posix多线程程序设计》 1.5.3章节,这里就不再搬运原文了。

接下来是移植成python版本的代码:

#!/usr/bin/env python3

from threading import Timer

class Alarm:
    def __init__(self, seconds:int, message:str):
        self.seconds = seconds
        self.message = message

def callback(alarm:Alarm):
    print("({}) {}\n".format(alarm.seconds, alarm.message))

if __name__ == "__main__":
    while True:
        line = input("Alarm> ")
        if len(line) <= 1:
            continue

        try:
            seconds, *message = line.split(' ')
            alarm = Alarm(int(seconds), ' '.join(message))
            t = Timer(interval=int(seconds), function=callback, args=(alarm, ))
            t.start()
        except:
            print("Bad command")

python版本的代码大家有兴趣的可以在本地运行一下,看看效果;)

再然后,我把这段代码翻译成C++版本的,基于C++的Timer类接口:

cpp

#include "timer.hpp"
#include <cstdlib>
#include <string>
#include <memory>
#include <iostream>

struct Alarm {
    Alarm(int seconds_, const std::string& message_): 
        seconds(seconds_), message(message_) {
    }

    int seconds;
    std::string message;
};

void callback(std::shared_ptr<Alarm> alarm) {
    std::cout << "(" << alarm->seconds << ") " << alarm->message << std::endl;
}

std::tuple<int, std::string> parse_command(const std::string& line) {
    auto pos = line.find(' ');
    if (pos == std::string::npos)
        throw std::runtime_error("invalid line: separator not found");

    int seconds = std::stoi(line.substr(0, pos));
    std::string message = line.substr(pos+1);
    return std::make_tuple(seconds, message);
}

int main()
{
    std::string line;
    int seconds;
    std::string message;
    while (true) {
        std::cout << "Alarm> ";
        if (!std::getline(std::cin, line)) exit(0);
        if (line.length() <= 1) continue;

        try {
            std::tie(seconds, message) = parse_command(line);
            auto alarm = std::make_shared<Alarm>(seconds, message);
            Timer t(seconds, std::bind(callback, alarm));
            t.start();
        } 
        catch (const std::exception& e) {
            std::cout << "Bad command" << std::endl;
        }
    }
}

这样我们就有了C++版本Timer类的测试驱动程序,并且可以跟C和python版本的代码对比运行。

接下来给出C++版本的基于创建线程的Timer实现:

cpp

#pragma once

#include <thread>
#include <chrono>
#include <atomic>
#include <functional>

class Timer {
public:
    typedef std::function<void ()> Callback;

    Timer(int interval, Callback function) {
        pimpl = std::make_shared<Impl>(interval, function);
    }

    void start() {
        std::thread t([pimpl=pimpl]() {
            if(!pimpl->active.load()) return;
            std::this_thread::sleep_for(std::chrono::seconds(pimpl->interval));
            if(!pimpl->active.load()) return;
            pimpl->function();
        });
        t.detach();
    }

    void cancel() {
        pimpl->active.store(false);
    }

private:
    struct Impl {
        Impl(int interval_, Callback function_): interval(interval_), function(function_) {}

        int interval;
        Callback function;
        std::atomic<bool> active{true};
    };

private:
    std::shared_ptr<Impl> pimpl;
};

C++实现部分,基本上是C版本的代码抽离和封装,并把相关函数替换成C++标准库的实现而已。不过Timer类麻雀虽小,但五脏俱全,其中用到的C++标准库组件有:

  • std::function:用于抽象到期回调函数
  • std::shared_ptr:用于管理Timer::Impl的生命周期
  • std::atomic:用于cancel Timer的flag,保证线程安全
  • std::thread:用于Timer线程,sleep指定时间,然后调用回调函数
  • std::chrono:C++标准库中时间相关的实现都在其中
  • C++ lambda:Timer线程的target函数,捕获了this->pimpl,保证了Timerl::Impl对象不会因为Timer对象的析构而析构

这里还用的了Pimpl惯用法,虽然目前是把接口和实现都放在了头文件里,但标准的做法是Timer的成员函数实现和Timer::Impl实现都放到源文件中,
这样头文件里可以去掉除了std::shared_ptr和std::function以外的依赖。

这个Timer类的实现优缺点是十分明显的:优点是代码简洁,一目了然,缺点是太过简洁,每个Timer需要一个线程去运行,系统资源消耗大。
于是就引出了基于条件变量版本的Timer,实现“参考”了《Posix多线程程序设计》3.3.4章节提到闹钟实例的最终版本(与其说“参考”,改成“直译”也不为过;)。

照例,我先给出基于pthread的条件变量版本的实例代码,C代码:

c

/*
 * alarm_cond.c
 *
 * This is an enhancement to the alarm_mutex.c program, which
 * used only a mutex to synchronize access to the shared alarm
 * list. This version adds a condition variable. The alarm
 * thread waits on this condition variable, with a timeout that
 * corresponds to the earliest timer request. If the main thread
 * enters an earlier timeout, it signals the condition variable
 * so that the alarm thread will wake up and process the earlier
 * timeout first, requeueing the later request.
 */
#include <pthread.h>
#include <time.h>
#include "errors.h"

/*
 * The "alarm" structure now contains the time_t (time since the
 * Epoch, in seconds) for each alarm, so that they can be
 * sorted. Storing the requested number of seconds would not be
 * enough, since the "alarm thread" cannot tell how long it has
 * been on the list.
 */
typedef struct alarm_tag {
    struct alarm_tag    *link;
    int                 seconds;
    time_t              time;   /* seconds from EPOCH */
    char                message[64];
} alarm_t;

pthread_mutex_t alarm_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t alarm_cond = PTHREAD_COND_INITIALIZER;
alarm_t *alarm_list = NULL;
time_t current_alarm = 0;

/*
 * Insert alarm entry on list, in order.
 */
void alarm_insert (alarm_t *alarm)
{
    int status;
    alarm_t **last, *next;

    /*
     * LOCKING PROTOCOL:
     * 
     * This routine requires that the caller have locked the
     * alarm_mutex!
     */
    last = &alarm_list;
    next = *last;
    while (next != NULL) {
        if (next->time >= alarm->time) {
            alarm->link = next;
            *last = alarm;
            break;
        }
        last = &next->link;
        next = next->link;
    }
    /*
     * If we reached the end of the list, insert the new alarm
     * there.  ("next" is NULL, and "last" points to the link
     * field of the last item, or to the list header.)
     */
    if (next == NULL) {
        *last = alarm;
        alarm->link = NULL;
    }
#ifdef DEBUG
    printf ("[list: ");
    for (next = alarm_list; next != NULL; next = next->link)
        printf ("%d(%d)[\"%s\"] ", next->time,
            next->time - time (NULL), next->message);
    printf ("]\n");
#endif
    /*
     * Wake the alarm thread if it is not busy (that is, if
     * current_alarm is 0, signifying that it's waiting for
     * work), or if the new alarm comes before the one on
     * which the alarm thread is waiting.
     */
    if (current_alarm == 0 || alarm->time < current_alarm) {
        current_alarm = alarm->time;
        status = pthread_cond_signal (&alarm_cond);
        if (status != 0)
            err_abort (status, "Signal cond");
    }
}

/*
 * The alarm thread's start routine.
 */
void *alarm_thread (void *arg)
{
    alarm_t *alarm;
    struct timespec cond_time;
    time_t now;
    int status, expired;

    /*
     * Loop forever, processing commands. The alarm thread will
     * be disintegrated when the process exits. Lock the mutex
     * at the start -- it will be unlocked during condition
     * waits, so the main thread can insert alarms.
     */
    status = pthread_mutex_lock (&alarm_mutex);
    if (status != 0)
        err_abort (status, "Lock mutex");
    while (1) {
        /*
         * If the alarm list is empty, wait until an alarm is
         * added. Setting current_alarm to 0 informs the insert
         * routine that the thread is not busy.
         */
        current_alarm = 0;
        while (alarm_list == NULL) {
            status = pthread_cond_wait (&alarm_cond, &alarm_mutex);
            if (status != 0)
                err_abort (status, "Wait on cond");
            }
        alarm = alarm_list;
        alarm_list = alarm->link;
        now = time (NULL);
        expired = 0;
        if (alarm->time > now) {
#ifdef DEBUG
            printf ("[waiting: %d(%d)\"%s\"]\n", alarm->time,
                alarm->time - time (NULL), alarm->message);
#endif
            cond_time.tv_sec = alarm->time;
            cond_time.tv_nsec = 0;
            current_alarm = alarm->time;
            while (current_alarm == alarm->time) {
                status = pthread_cond_timedwait (
                    &alarm_cond, &alarm_mutex, &cond_time);
                if (status == ETIMEDOUT) {
                    expired = 1;
                    break;
                }
                if (status != 0)
                    err_abort (status, "Cond timedwait");
            }
            if (!expired)
                alarm_insert (alarm);
        } else
            expired = 1;
        if (expired) {
            printf ("(%d) %s\n", alarm->seconds, alarm->message);
            free (alarm);
        }
    }
}

int main (int argc, char *argv[])
{
    int status;
    char line[128];
    alarm_t *alarm;
    pthread_t thread;

    status = pthread_create (
        &thread, NULL, alarm_thread, NULL);
    if (status != 0)
        err_abort (status, "Create alarm thread");
    while (1) {
        printf ("Alarm> ");
        if (fgets (line, sizeof (line), stdin) == NULL) exit (0);
        if (strlen (line) <= 1) continue;
        alarm = (alarm_t*)malloc (sizeof (alarm_t));
        if (alarm == NULL)
            errno_abort ("Allocate alarm");

        /*
         * Parse input line into seconds (%d) and a message
         * (%64[^\n]), consisting of up to 64 characters
         * separated from the seconds by whitespace.
         */
        if (sscanf (line, "%d %64[^\n]", 
            &alarm->seconds, alarm->message) < 2) {
            fprintf (stderr, "Bad command\n");
            free (alarm);
        } else {
            status = pthread_mutex_lock (&alarm_mutex);
            if (status != 0)
                err_abort (status, "Lock mutex");
            alarm->time = time (NULL) + alarm->seconds;
            /*
             * Insert the new alarm into the list of alarms,
             * sorted by expiration time.
             */
            alarm_insert (alarm);
            status = pthread_mutex_unlock (&alarm_mutex);
            if (status != 0)
                err_abort (status, "Unlock mutex");
        }
    }
}

代码有些长,代码说明见《Posix多线程程序设计》3.3.4章节,我这里啰嗦几句,简单总结一下,基于条件变量实现定时器的原理:

  • 程序维护了一个有序的定时器列表,顺序按照到期时间从小到大排列
  • 独立的alarm_thread线程函数,实现定时器到期回调的逻辑,通过条件变量的pthread_cond_wait函数,处理定时器列表为空的情况,以及等待队列中最早的定时器到期,以及有更早的定时器插入队列的情况
  • 函数alarm_insert函数,实现将定时器插入到定时器列表的逻辑,并在定时器列表原先为空的情况下,或者在插入的定时器到期时间早于定时间列表中最早到期时,通知alarm_thread线程,唤醒pthread_cond_wait函数。

由于基于pthread的条件变量版本的实例代码中,增加了DEBUG宏和调试代码,所以我的C++版本的测试驱动程序也需要做相应的调整:

cpp

#include "timer.hpp"
#include <cstdlib>
#include <string>
#include <memory>
#include <iostream>

struct Alarm {
    Alarm(int seconds_, const std::string& message_): 
        seconds(seconds_), message(message_) {
    }

    int seconds;
    std::string message;
};

void callback(std::shared_ptr<Alarm> alarm) {
    std::cout << "(" << alarm->seconds << ") " << alarm->message << std::endl;
}

std::tuple<int, std::string> parse_command(const std::string& line) {
    auto pos = line.find(' ');
    if (pos == std::string::npos)
        throw std::runtime_error("invalid line: separator not found");

    int seconds = std::stoi(line.substr(0, pos));
    std::string message = line.substr(pos+1);
    return std::make_tuple(seconds, message);
}

int main()
{
    std::string line;
    int seconds;
    std::string message;
    while (true) {
        std::cout << "Alarm> ";
        if (!std::getline(std::cin, line)) quick_exit(0);
        if (line.length() <= 1) continue;

        try {
            std::tie(seconds, message) = parse_command(line);
            auto alarm = std::make_shared<Alarm>(seconds, message);
            Timer t(seconds, std::bind(callback, alarm));
#ifdef DEBUG
            t.set_message(message);
#endif
            t.start();
        } 
        catch (const std::exception& e) {
            std::cout << "Bad command" << std::endl;
        }
    }
}

其实修改就只有两处: - 将exit改成了quick_exit(因为下面的C++实现里,对应于C版本的alarm_thread线程是detach的,exit时会挂起在cond的wait上) - 增加t.set_message调用,用来打印debug信息

alarm_cond.png

接下来给出条件变量版本的C++的Timer类实现,如果C版本的代码看得懂,那C++版本的代码就可以说是一目了然,毕竟C++的版本是“直译”过来的。

cpp

#pragma once

#include <functional>
#include <memory>

class Timer {
public:
    typedef std::function<void ()> Callback;

    Timer(int interval, Callback function); 
    void start();
    void cancel(); 

#ifdef DEBUG
    void set_message(const std::string& message);
#endif

public:
    struct Impl; 

private:
    std::shared_ptr<Impl> pimpl;
};

首先来看Timer的头文件,这里就看出Pimpl惯用法的优势了,头文件里完全剥离了对<chrono>、<thread>、<atomic>的依赖。 另外,增加了set_message接口,用于实现C版本中DEBUG宏中的调试信息打印

下面是Timer的源文件,代码和C版本的一样,有点儿长:

cpp

#include "timer.hpp"
#include <list>
#include <memory>
#include <chrono>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <thread>

#ifdef DEBUG
#include <iostream>
#endif

using Clock = std::chrono::system_clock;
using TimePoint = Clock::time_point; 
using Seconds = std::chrono::seconds;
using AlarmPtr = std::shared_ptr<Timer::Impl>;

#ifdef DEBUG
namespace {

std::ostream& operator<<(std::ostream& out, const TimePoint& tp) {
    using namespace std::chrono;
    auto d = duration_cast<seconds>(tp.time_since_epoch());
    out << d.count();
    return out;
}

template <typename Rep, typename Preiod>
std::ostream& operator<<(std::ostream& out, const std::chrono::duration<Rep, Preiod>& d) {
    using namespace std::chrono;
    auto s = duration_cast<seconds>(d);
    out << s.count();
    return out;
}

}   // namespace
#endif

class Timer::Impl {
public:
    Impl(int interval_, Callback function_): interval(interval_), function(function_) {
    }

    void setup_alarm() {
        time = Clock::now() + Seconds(interval);
    }

    int interval;
    Timer::Callback function;
    TimePoint time;
    std::atomic<bool> active{true};

#ifdef DEBUG
    std::string message;
#endif
};

class AlarmLooper {
public:
    AlarmLooper() = default; 
    AlarmLooper(const AlarmLooper&) = delete;
    void operator=(const AlarmLooper&) = delete;

    void thread_safety_insert(AlarmPtr alarm) {
        std::unique_lock<std::mutex> lock(alarm_mutex);
        insert(alarm);
    }

    void insert(AlarmPtr alarm);
    void run();

private:
    std::list<AlarmPtr> alarm_list;
    TimePoint current_alarm;
    std::mutex alarm_mutex;
    std::condition_variable alarm_cond;
};

/*
 * Insert alarm entry on list, in order.
 */
void AlarmLooper::insert(AlarmPtr alarm) {
    auto first = alarm_list.begin();
    auto last = alarm_list.end();

    /*
     * LOCKING PROTOCOL:
     * 
     * This routine requires that the caller have locked the
     * alarm_mutex!
     */
    for ( ; first != last; ++first) {
        if ((*first)->time >= alarm->time) {
            alarm_list.insert(first, alarm);
            break;
        }
    }

    /*
     * If we reached the end of the list, insert the new alarm there. 
     */
    if (first == last) {
        alarm_list.push_back(alarm);
    }
	
#ifdef DEBUG
    std::cout << "[list:";
    for (auto item : alarm_list) {
        std::cout << item->time << "(" << (item->time - Clock::now()) << ")[\""
                << item->message << "\"] ";
    }
    std::cout << "]\n" << std::flush;
#endif

    /*
     * Wake the alarm thread if it is not busy (that is, if
     * current_alarm is 0, signifying that it's waiting for
     * work), or if the new alarm comes before the one on
     * which the alarm thread is waiting.
     */
    if (current_alarm == TimePoint{} || alarm->time < current_alarm) {
        current_alarm = alarm->time;
        alarm_cond.notify_one();
    }
}

/*
 * The alarm thread's start routine.
 */
void AlarmLooper::run() {
    AlarmPtr alarm;
    TimePoint now;
    bool expired;
    std::cv_status status;

    /*
     * Loop forever, processing commands. The alarm thread will
     * be disintegrated when the process exits. Lock the mutex
     * at the start -- it will be unlocked during condition
     * waits, so the main thread can insert alarms.
     */
    std::unique_lock<std::mutex> lock(alarm_mutex);
    while (true) {
        /*
         * If the alarm list is empty, wait until an alarm is
         * added. Setting current_alarm to 0 informs the insert
         * routine that the thread is not busy.
         */
        current_alarm = TimePoint{};
        while (alarm_list.empty()) {
            alarm_cond.wait(lock);
        }

        alarm = alarm_list.front();
        alarm_list.pop_front();
        now = Clock::now();
        expired = false;
        if (alarm->time > now) {
#ifdef DEBUG
            std::cout << "[waiting: " << alarm->time << "(" << (alarm->time - Clock::now()) << ")\""
                << alarm->message << "\"\n" << std::flush; 
#endif
            current_alarm = alarm->time;
            while (current_alarm == alarm->time) {
                status = alarm_cond.wait_until(lock, alarm->time);
                if (status == std::cv_status::timeout) {
                    expired = true;
                    break;
                } 
            }
            if (!expired) {
                insert(alarm);
            }
        } else {
            expired = true;
        }

        if (expired) {
            if (alarm->active) {
                alarm->function();
            }
        }
    }
}

class TimerThread {
public:
    TimerThread(); 
    void insert_alarm(std::shared_ptr<Timer::Impl> timer);
    bool is_in_looper_thread() {
        return std::this_thread::get_id() == looper_thread.get_id();
    }

    static TimerThread& get_instance() {
        static TimerThread timer_thread;
        return timer_thread;
    }

private:
    AlarmLooper alarm_looper;
    std::thread looper_thread;
};

TimerThread::TimerThread() {
    looper_thread = std::thread(&AlarmLooper::run, &alarm_looper);
    looper_thread.detach();
}

void TimerThread::insert_alarm(std::shared_ptr<Timer::Impl> pimpl) {
    if (is_in_looper_thread()) {
        alarm_looper.insert(pimpl);
    } else {
        alarm_looper.thread_safety_insert(pimpl);
    }
}

Timer::Timer(int interval, Callback function) {
    pimpl = std::make_shared<Impl>(interval, function);
}

void Timer::start() {
    pimpl->setup_alarm();
    TimerThread::get_instance().insert_alarm(pimpl);
}

void Timer::cancel() {
    pimpl->active = false;
}

#ifdef DEBUG
void Timer::set_message(const std::string& message) {
    pimpl->message = message;
}
#endif

实现原理和C版本的一致(要不说是“直译”呢;),具体的映射关系如下:

  • AlarmLooper类:封装了C版本的函数alarm_thread、alarm_insert逻辑,AlarmLooper::run对应alarm_thread,AlarmLooper::insert对应alarm_insert
  • TimerThread类:作为单例类,管理运行AlarmLooper::run的线程
  • 然后就是std::list替代了C手写的链表
  • std::mutex和std::conditon_variable替代了pthread_mutex_t和pthread_cond_t结构体和函数。

最后,我来简单说明一下我为什么实现C++版本Timer类,以及可以改进优化的方向。

理由方面:

  • C版本的代码很优秀,通过将C版本的代码移植并封装成C++版本,可以提高我对timer实现的算法理解,封装过程也是对C++的OOP的一次实践
  • 实现为Python的threading.Timer接口,则是认识到python接口的简洁性
  • Timer类具备一定的通用性,可以在其他项目中复用,考虑到目前C++标准库(截至C++20)中还没有类似的timer类,以及其他第三方库(例如boost,libuv等)都相对比较重
  • C++版本依赖于C++标准库,而C++标准库是跨平台的,所以Timer类也是跨平台的(当然,如果把C版本的pthread函数替换成C11标准库的线程函数也能达到同样目的)

改进方面:

  • 条件变量版本的C++实现中,alarm_looper的线程是detach的,造成主线程exit时,alarm_looper的线程会因为cond的wait,把整个进程挂起, 所以需要在AlarmLooper增加stop()接口及实现,使得可以通知AlarmLooper::run结束循环并返回(从而使alarm_looper的线程退出),然后, TimerThread类里,alarm_looper的线程不是detach的,而是在TimerThread类的析构函数里通知alarm_looper的线程stop,并join它。
  • 到期时间的精度是秒级的,实际应用中往往需要更高精度的timer
  • std::list可以替换某些已序的数据结构(比如std::multi_set)以提高性能,list的平均插入时间(保证有序的前提下)时O(n)的

最后的最后,给出文章中提到的所有代码的完整实现链接:

  • 基于thread的C++的Timer类实现,精度秒级,工程代码
  • 基于thread的C++的Timer类实现,精度微秒级,工程代码
  • 基于条件变量的C++的Timer类实现,精度秒级,工程代码
  • 基于条件变量的C++的Timer类实现,精度秒级,并增加了AlarmLooper的stop接口,工程代码
  • 基于条件变量的C++的Timer类实现,精度微秒级,并增加了AlarmLooper的stop接口,工程代码

2024/02/08日追加内容:
一般的Timer的接口都是支持一次性(alarm once)和周期性(alarm period)两种模式的,python的threading.Timer接口显然只支持alarm once模式, 虽然也可以通过在alarm once回调函数里再次创建timer的方式等价的实现alarm period模式, 不过我最近参考A Simple Timer in C++的接口,实现了支持alarm period模式的Timer, 这里也一并分享出来

  • A Simple Timer in C++原始版本:基于thread实现,接口为毫秒级精度,工程代码
  • 我的版本-1:基于thread实现,接口参数改成C++11的chrono::duration类型,工程代码
  • 我的版本-2:基于条件变量实现,接口同A Simple Timer in C++,也为毫秒级精度,工程代码
  • 我的版本-3:基于条件变量实现,接口同版本-1,接口参数改成C++11的chrono::duration类型,工程代码

参考文档:

  • 《Posix多线程程序设计(Programming with POSIX Threads)》
  • 3.11.0 Documentation » The Python Standard Library » Concurrent Execution » threading
  • A Simple Timer in C++

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1872864.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于JVM必备的一些知识

一、类加载 【加载】 - 【链接】-【初始化】 1.1 加载&#xff08;Loading&#xff09; 加载阶段是类加载过程的第一步&#xff0c;它的主要任务是通过类的全限定名&#xff08;Fully Qualified Class Name&#xff09;来获取类的二进制字节流&#xff08;binary data&#…

告别 “屎山” 代码,务必掌握这14 个 SpringBoot 优化小妙招

插&#xff1a; AI时代&#xff0c;程序员或多或少要了解些人工智能&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家(前言 – 人工智能教程 ) 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家…

PCB在工业领域的应用以及人工智能的影响。

什么是pcb呢? PCB,全称Printed Circuit Board,中文名称为印制电路板,也被称为印刷线路板或印制板1。这是一种重要的电子部件,主要由绝缘基板、连接导线和装配焊接电子元器件的焊盘组成。PCB的主要作用是作为电子元器件的支撑体和电气连接的载体,它能够简化电子产品的装配…

爬虫:爬取知乎热榜一级评论及回答2024不包含翻页

一、先上结果&#xff08;注:本文仅为兴趣爱好探究&#xff0c;请勿进行商业利用或非法研究&#xff0c;负责后果自负&#xff0c;与作者无关&#xff09; 1、爬标题及其具体内容 2、抓标题下的对应回答 3、爬取对应一级评论 二、上流程 1、获取cookies&#xff08;相信哥哥姐姐…

Edge 浏览器全解析:实用技巧让您的浏览体验更上一层楼

在当今数字化的时代&#xff0c;浏览器成为了我们获取信息、工作和娱乐的重要工具。微软 Edge 浏览器作为一款备受瞩目的浏览器&#xff0c;拥有许多强大的功能和实用的使用技巧。在这篇博客中&#xff0c;让我们深入探索 Edge 浏览器&#xff0c;助您充分发挥其潜力。 一、高效…

Ubuntu20.04安装LibTorch并完成高斯溅射环境搭建

0. 简介 最近受到优刻得的使用邀请&#xff0c;正好解决了我在大模型和自动驾驶行业对GPU的使用需求。UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU&#xff0c;按时收费每卡2.6元&#xff0c;月卡只需要1.7元每小时&#xff0c;并附带200G的免费…

x86 平台实现一个原子加法操作

1&#xff0c;先上代码 #include <iostream> #include <omp.h>int atomicAdd(int* ptr, int value) {int result;asm volatile("lock xaddl %0, %1\n": "r" (result), "m" (*ptr): "0" (value), "m" (*ptr): &…

关于IDEA启动报错 【JAVA_HOME does not point to a valid JM installation】

希望文章能给到你启发和灵感&#xff5e; 感谢支持和关注&#xff5e; 阅读指南 一、基础环境说明1.1 硬件环境1.2 软件环境 二、起因 一、基础环境说明 考虑环境因素不同&#xff0c;大家适当的对比自己的软硬件环境情况分析&#xff5e; 1.1 硬件环境 MacOS Monterey 版本 1…

海南云亿商务咨询有限公司深度解读抖音电商

在当今数字化飞速发展的时代&#xff0c;电商行业早已成为经济发展的重要引擎。而在众多电商平台中&#xff0c;抖音以其独特的短视频直播形式&#xff0c;成为了众多商家和消费者的新宠。海南云亿商务咨询有限公司&#xff0c;正是这一领域的佼佼者&#xff0c;专注于抖音电商…

分享暄桐林曦老师的精进心法

暄桐是一间传统美学教育教室&#xff0c;创办于2011年&#xff0c;林曦是创办人和授课老师&#xff0c;教授以书法为主的传统文化和技艺&#xff0c;皆在以书法为起点&#xff0c;亲近中国传统之美&#xff0c;以实践和所得&#xff0c;滋养当下生活。      清风雅致林曦老…

基于Vue3 + Typescript 封装 Element-Plus 组件

1. 课程简介 项目地址 git clone https://gitee.com/childe-jia/my-message.git 背景: 该课程是基于Vue3 Typescript Vite构建, 教会大家封装Element-Plus组件 具备能力: 最新的 Vue3 及相关技术组件的设计思想大厂的开发模式/代码规范 技术: Vue3 首次渲染 / diff 算法 …

Transformer基础及视觉应用

文章目录 Transformer基础及视觉应用注意力机制基础(主要介绍Transformer用到的类型)Transformer的编解码器结构(Encoder and Decoder)Non-local Neural NetworksTransformer与大规模图像识别(Image Recognition at Scale)DETR-2020分割应用 Transformer基础及视觉应用 注意力…

小区物业服务差,现在催缴物业费,暂时不想交如何应对?

面对催缴物业费的情况&#xff0c;采取合理、合法的方式进行沟通和处理是非常重要的。如果您认为物业服务存在不足或者问题&#xff0c;可以按照以下步骤尝试解决问题&#xff0c;而不是直接拒绝缴费&#xff0c;避免后续可能产生的法律纠纷&#xff1a; 收集证据&#xff1a;首…

电脑怎么去除视频水印?电脑视频水印怎么去掉?

电脑怎么去除视频水印&#xff1f;有是我们见到喜欢的视频会保存下来&#xff0c;但是有时候保存的视频上面会带有水印&#xff0c;那么视频水印该如何去除呢&#xff1f;今天小编给大家推荐一个好用的视频去水印软件&#xff0c;操作简单&#xff0c;去水印效果好。 使用&…

tauri使用github action实现跨平台编译并解决编译错误等问题

正常编译为跨平台结果就像上面的&#xff0c;有mac/windows/linux的安装程序&#xff0c;直接下载就可以安装使用&#xff0c;我的这个livebox桌面端仓库地址&#xff1a;GitHub - Sjj1024/LiveBox: livebox&#xff0c;里面有编译文件可以参考。今天主要讲一下遇到的问题。 官…

碳课堂|ISO 14064标准新版变化

ISO 14064标准是针对组织碳排放方面的管理标准&#xff0c;包括温室气体排放和清除的量化、报告与验证的国际标准。其最新版本于 2018年发布&#xff0c;标志着对温室气体管理的全球认知和实践的进一步演进。ISO 14064 作为 ISO 14060 标准系列的重要组成部分&#xff0c;将继续…

bodypaint如何恢复布局设置

1.老师我手贱&#xff0c;布局改了&#xff0c;怎么恢复 2.左边咋没有纹理这个窗口了用来放参考图的 窗口&#xff0c;新建纹理视图&#xff0c;点那九点&#xff0c;拖拽&#xff0c;改变悬浮窗的状态

CatBoost原理介绍

文章最前&#xff1a; 我是Octopus&#xff0c;这个名字来源于我的中文名–章鱼&#xff1b;我热爱编程、热爱算法、热爱开源。所有源码在我的个人github &#xff1b;这博客是记录我学习的点点滴滴&#xff0c;如果您对 Python、Java、AI、算法有兴趣&#xff0c;可以关注我的…

专业好用的数据恢复软件(iTop Data Recovery Pro v4.4.0.687 激活版 )

前言 由于 iTop 独特的恢复算法&#xff0c;这个强大的数据恢复工具加快了扫描和数据恢复过程。您无需等待很长时间即可取回数据。只需几分钟&#xff0c;您就可以按预期完美找回已删除的文件。此外&#xff0c;扫描选项多种多样&#xff0c;可满足不同的需求。您可以通过位置…

《昇思25天学习打卡营第3天 | 昇思MindSpore数据集 Dataset》

第三天 今天学习了不同的数据集加载方式、数据集常见操作和自定义数据集方法。 1.数据集加载。 以Mnist数据集为例。mindspore.dataset提供的接口仅支持解压后的数据文件&#xff0c;因此我们使用download库下载数据集并解压。 2.数据集迭代。 用create_tuple_iterator或create…