【Linux】timerfd——定时器

news2024/11/15 22:44:00

文章目录

    • 前言
    • 认识 timerfd
      • API timerfd
      • API clock
    • 官方示例
    • 简单使用
    • epoll实现

前言

在 Linux 系统编程中,使用 timerfd 实现定时器功能是一种更加可靠、高效、灵活的方式。本文是对 timerfd 的简单使用,不涉及太过深入知识,熟练掌握几个常用 API,实现定时器功能。

认识 timerfd

timerfd 是 Linux 为用户程序提供的一个定时器接口。这个接口基于文件描述符,通过文件描述符的可读事件进行超时通知,它可以用来实现高精度的定时器,以及与 I/O 多路复用机制(如selectpollepoll等)进行配合,实现异步事件驱动的程序。

优点:

  1. 高精度:timerfd使用内核提供的高精度计时器,精度通常可以达到纳秒级别,相对于使用系统时间的方式,误差更小,定时器的精度更高。

  2. 资源占用低:timerfd使用内核提供的异步I/O机制,可以将定时器超时事件通知给应用程序,而不需要应用程序不断地轮询,从而避免了大量的CPU资源占用,提高了系统性能。

  3. 灵活性强:timerfd可以设置不同的定时器超时时间,并且可以同时管理多个定时器,可以根据应用程序的需要进行灵活配置。

  4. 可移植性好:timerfd是Linux内核提供的标准接口,可以在不同的Linux系统上使用,具有很好的可移植性。

API timerfd

常用的 API 接口:

#include <sys/timerfd.h>

int timerfd_create(int clockid, int flags);

int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);

int timerfd_gettime(int fd, struct itimerspec *curr_value);
  • timerfd_create
timerfd_create(CLOCK_REALTIME, 0); // 使用系统实时时间作为计时基准,阻塞模式。

clockid:
	CLOCK_REALTIME:使用系统实时时钟,可以被修改;
	CLOCK_MONOTONIC:使用系统单调时钟,不能被修改;
flags:
	TFD_NONBLOCK:非阻塞模式,read操作不会阻塞;
	TFD_CLOEXEC:创建的文件描述符将在执行exec时关闭。
  • timerfd_settime
timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL); 
// 计时器文件描述符fd,初始状态new_value,并且采用绝对时间。(0:相对,1:绝对)

fd:   		 timerfd_create函数创建的定时器文件描述符
flags:		 可以设置为0或者TFD_TIMER_ABSTIME,用于指定定时器的绝对时间或者相对时间
new_value:	 是一个指向itimerspec结构体的指针,用于指定新的定时器参数;
old_value:  是一个指向itimerspec结构体的指针,用于获取旧的定时器参数。
  • timerfd_gettime
// timerfd_gettime是一个用于获取timerfd定时器的当前超时时间的系统调用。
// 它可以用于查询定时器的当前状态,以便在需要时进行调整。

curr_value是指向itimerspec结构体的指针,用于存储当前定时器的超时时间和间隔。

  • itimerspec 结构体定义如下:
struct timespec {
    time_t tv_sec;   // seconds
    long   tv_nsec;  // nanoseconds
};

struct itimerspec {
    struct timespec it_interval;  // Interval for periodic timer
    struct timespec it_value;     // Initial expiration
};

API clock

#include <time.h>
int clock_settime(clockid_t clockid, const struct timespec *tp);
int clock_gettime(clockid_t clockid, struct timespec *tp);
  • clock_settime
// 用于设置系统时钟的时间
// clock_settime函数所设置的时间值会影响到所有基于该时钟的时间函数,例如gettimeofday、clock_gettime等。
// 在Linux系统中,只有特权进程(即具有root权限的进程)才能修改系统时钟。
  • clock_gettime
clock_gettime(CLOCK_MONOTONIC, &start); // 基于系统启动时间的单调递增时钟,获取到的结构体值赋给start

clockid:
	CLOCK_MONOTONIC:提供了一个单调递增的时钟,不受系统时间被改变的影响。
	CLOCK_REALTIME:供了当前的日期和时间,但是受系统时间被改变的影响。

官方示例

官方简单示例:(man手册查看)

#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#include <inttypes.h>      /* Definition of PRIu64 */
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>        /* Definition of uint64_t */

#define handle_error(msg) \
        do { perror(msg); exit(EXIT_FAILURE); } while (0)

static void
print_elapsed_time(void)
{
    static struct timespec start; // 静态,只用于第一次赋值
    struct timespec curr;
    static int first_call = 1;
    int secs, nsecs;

    if (first_call) {
        first_call = 0;
        if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
            handle_error("clock_gettime");
    }

    if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1)
        handle_error("clock_gettime");

    secs = curr.tv_sec - start.tv_sec;
    nsecs = curr.tv_nsec - start.tv_nsec;
    if (nsecs < 0) {
        secs--;
        nsecs += 1000000000;
    }
    printf("%d.%03d: ", secs, (nsecs + 500000) / 1000000);
}

int
main(int argc, char *argv[])
{
    struct itimerspec new_value;
    int max_exp, fd;
    struct timespec now;
    uint64_t exp, tot_exp;
    ssize_t s;

    if ((argc != 2) && (argc != 4)) {
        fprintf(stderr, "%s init-secs [interval-secs max-exp]\n", // 这里需要输入参数:(第一次到期,间隔,次数)
                argv[0]);
        exit(EXIT_FAILURE);
    }

    if (clock_gettime(CLOCK_REALTIME, &now) == -1)
        handle_error("clock_gettime");

    /* Create a CLOCK_REALTIME absolute timer with initial
        expiration and interval as specified in command line */

    new_value.it_value.tv_sec = now.tv_sec + atoi(argv[1]);
    new_value.it_value.tv_nsec = now.tv_nsec;
    if (argc == 2) {
        new_value.it_interval.tv_sec = 0;
        max_exp = 1;
    } else {
        new_value.it_interval.tv_sec = atoi(argv[2]);
        max_exp = atoi(argv[3]);
    }
    new_value.it_interval.tv_nsec = 0;

    fd = timerfd_create(CLOCK_REALTIME, 0); 
    if (fd == -1)
        handle_error("timerfd_create");

    if (timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL) == -1)
        handle_error("timerfd_settime");

    print_elapsed_time();
    printf("timer started\n");

    for (tot_exp = 0; tot_exp < max_exp;) {
        s = read(fd, &exp, sizeof(uint64_t));
        if (s != sizeof(uint64_t))
            handle_error("read");

        tot_exp += exp;
        print_elapsed_time();
        printf("read: %" PRIu64 "; total=%" PRIu64 "\n", exp, tot_exp);
    }

    exit(EXIT_SUCCESS);
}

简单使用

timerfd 的基本使用:

#include <stdio.h>
#include <errno.h> // EAGAIN
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>
#include <pthread.h>
#include <inttypes.h>
#include <sys/time.h>
#include <sys/timerfd.h>

#define handle_error(msg) \
        do { perror(msg); exit(EXIT_FAILURE); } while (0)

int g_exp = 1; // 第四个参数,执行次数
int g_timerfd = -1; // 计时器fd
struct timespec g_current; // 当前时间结构体
bool g_running = true;

void stop_timer(int); // 前置声明

void signal_handler(int signum) {
    stop_timer(g_timerfd);
    close(g_timerfd);
    g_timerfd = -1;
    g_running = false;
    // exit(EXIT_FAILURE);
}

// man timerfd_create
int create_timer() {
    // int fd = timerfd_create(CLOCK_MONOTONIC, 0);
    int fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
    if (fd == -1) 
        handle_error("create_timer failed");
    return fd;
}

void init_timer(int timerfd, time_t secs, long nsecs, time_t interval_secs, long interval_nsecs) {
    struct itimerspec new_value;
    new_value.it_value.tv_sec = secs;
    new_value.it_value.tv_nsec = nsecs;

    new_value.it_interval.tv_sec = interval_secs;
    new_value.it_interval.tv_nsec = interval_nsecs;
    
    if (timerfd_settime(timerfd, 0, &new_value, NULL) == -1)
        handle_error("init_time failed");
}

void stop_timer(int timerfd) {
    struct itimerspec new_value;
    new_value.it_value.tv_sec = 0;
    new_value.it_value.tv_nsec = 0;

    new_value.it_interval.tv_sec = 0;
    new_value.it_interval.tv_nsec = 0;

    if (timerfd_settime(timerfd, 0, &new_value, NULL) == -1) 
        handle_error("stop_timer failed");
}

// 打印间隔时间
static void print_elapsed_time() {
    static struct timespec start;
    static int first_call = 1; 

    if (first_call) {
        first_call = 0;
        if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
            handle_error("clock_gettime failed");
    }

    if (clock_gettime(CLOCK_MONOTONIC, &g_current) == -1)
        handle_error("clock_gettime failed");

    time_t secs = g_current.tv_sec - start.tv_sec;
    long  nsecs = g_current.tv_nsec - start.tv_nsec;
    if (nsecs < 0) {
        secs -= 1;
        nsecs += 1000000000;
    }
    printf("%ld.%03ld: ", secs, (nsecs + 500000) / 1000000);
}

// 工作线程
void *work_function(void *timerfd) {
    int fd = *(int *)timerfd; 

    uint64_t exp; 
    for (int cnt = 0; cnt < g_exp && g_running; cnt++) {
        int ret = read(fd, &exp, sizeof(uint64_t));
        if (ret == sizeof(uint64_t)) {
            print_elapsed_time();
        } else { // 非阻塞模式,死循环,消耗CPU
            cnt -= 1;
        }
    }
    return NULL;
}

int main(int argc, char *argv[]) {
    // 注册信号
    signal(SIGINT, signal_handler); // kill -l 

    if (argc != 4) {
        fprintf(stderr, "Usage %s init-secs interval-secs max-exp\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    g_timerfd = create_timer();
    init_timer(g_timerfd, atoi(argv[1]), 0, atoi(argv[2]), 0);
    g_exp = atoi(argv[3]);
    print_elapsed_time(); // 第一次

    pthread_t tid; 
    pthread_create(&tid, NULL, work_function, &g_timerfd);
    pthread_join(tid, NULL);
    return 0;   
}

上例程序是非阻塞的timerfd,极大占用CPU资源。

在这里插入图片描述


epoll实现

timerfd、eventfd、signalfd配合epoll使用,可以构造出一个零轮询的程序,但程序没有处理的事件时,程序是被阻塞的。epoll_wait阻塞监听事件。

  • eventfd
#include <sys/eventfd.h>
int eventfd(unsigned int initval, int flags);
// 其中initval参数是eventfd的初始值,通常设置为0即可,表示初始时没有事件发生。
// flags参数是eventfd的标志位,通常设置为0即可,表示没有特殊要求。
// 函数返回值是eventfd的文件描述符,可以用于后续的读写操作。

eventfd是Linux系统调用中的一种文件描述符类型,它可以用于在进程间传递事件通知。当一个进程调用eventfd创建一个事件文件描述符时,它会得到一个文件描述符,可以使用该文件描述符进行读写操作。当另一个进程写入数据到该文件描述符时,它会唤醒正在等待读取该文件描述符的进程。这样,可以实现进程间的事件通知,而无需使用显式的信号量或者管道等机制。

eventfd的使用非常简单,只需要调用eventfd函数创建一个事件文件描述符,然后使用read和write函数进行读写操作即可。eventfd还支持select、poll、epoll等多路复用机制,可以方便地将其集成到事件驱动的程序中。


代码如下:

  • timerfd设置为非阻塞状态:
fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
  • 创建eventfd
g_eventfd = eventfd(0, 0);
  • 注册epoll事件监听
int epollfd = epoll_create(2);
if (epollfd == -1)
    handle_error("epoll_create failed");

struct epoll_event ev_timer;
ev_timer.events = EPOLLIN;
ev_timer.data.fd = fd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev_timer);

struct epoll_event ev_event;
ev_event.events = EPOLLIN;
ev_event.data.fd = g_eventfd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, g_eventfd, &ev_event);

const int max_events = 2;
struct epoll_event events[max_events];

详见代码:

#include <stdio.h>
#include <errno.h> // EAGAIN
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>
#include <pthread.h>
#include <inttypes.h>
#include <sys/time.h>
#include <sys/epoll.h> 
#include <sys/eventfd.h>
#include <sys/timerfd.h>

#define handle_error(msg) \
        do { perror(msg); exit(EXIT_FAILURE); } while (0)

int g_exp = 1; // 第四个参数,执行次数
int g_timerfd = -1; // 计时器fd
struct timespec g_current; // 当前时间结构体
bool g_running = true;
int g_eventfd; // a file descriptor for event notification

void stop_timer(int); // 前置声明

void signal_handler(int signum) {
    stop_timer(g_timerfd);

    g_timerfd = -1;
    g_running = false;
    
    uint64_t g_eventfd_val = 10086; // 64位无符号!!!;不要用 -1
    write(g_eventfd, &g_eventfd_val, sizeof(uint64_t));
}

// man timerfd_create
int create_timer() {
    // int fd = timerfd_create(CLOCK_MONOTONIC, 0);
    int fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
    if (fd == -1) 
        handle_error("create_timer failed");
    return fd;
}

void init_timer(int timerfd, time_t secs, long nsecs, time_t interval_secs, long interval_nsecs) {
    struct itimerspec new_value;
    new_value.it_value.tv_sec = secs;
    new_value.it_value.tv_nsec = nsecs;

    new_value.it_interval.tv_sec = interval_secs;
    new_value.it_interval.tv_nsec = interval_nsecs;
    
    if (timerfd_settime(timerfd, 0, &new_value, NULL) == -1)
        handle_error("init_time failed");
}

void stop_timer(int timerfd) {
    struct itimerspec new_value;
    new_value.it_value.tv_sec = 0;
    new_value.it_value.tv_nsec = 0;

    new_value.it_interval.tv_sec = 0;
    new_value.it_interval.tv_nsec = 0;

    if (timerfd_settime(timerfd, 0, &new_value, NULL) == -1) 
        handle_error("stop_timer failed");
}

// 打印间隔时间
static void print_elapsed_time() {
    static struct timespec start;
    static int first_call = 1; 

    if (first_call) {
        first_call = 0;
        if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
            handle_error("clock_gettime failed");
    }

    if (clock_gettime(CLOCK_MONOTONIC, &g_current) == -1)
        handle_error("clock_gettime failed");

    time_t secs = g_current.tv_sec - start.tv_sec;
    long  nsecs = g_current.tv_nsec - start.tv_nsec;
    if (nsecs < 0) {
        secs -= 1;
        nsecs += 1000000000;
    }
    printf("%ld.%03ld\n", secs, (nsecs + 500000) / 1000000);
}

// 工作线程
void *work_function(void *timerfd) {
    int fd = *(int *)timerfd; 

    int epollfd = epoll_create(2);
    if (epollfd == -1)
        handle_error("epoll_create failed");
    
    struct epoll_event ev_timer;
    ev_timer.events = EPOLLIN;
    ev_timer.data.fd = fd;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev_timer);

    struct epoll_event ev_event;
    ev_event.events = EPOLLIN;
    ev_event.data.fd = g_eventfd;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, g_eventfd, &ev_event);

    const int max_events = 2;
    struct epoll_event events[max_events];

    uint64_t exp; 
    for (int cnt = 0; cnt < g_exp && g_running; cnt++) {
        int nfd = epoll_wait(epollfd, events, max_events, -1);

        if (nfd > 0) {
            for (int i = 0; i < nfd; i++) {
                if (events[i].data.fd == fd) {
                    int ret = read(fd, &exp, sizeof(uint64_t));
                    if (ret == sizeof(uint64_t)) {
                        print_elapsed_time();
                    }
                } else if (events[i].data.fd == g_eventfd) {
                    int ret = read(g_eventfd, &exp, sizeof(uint64_t));
                    if (ret == sizeof(uint64_t)) {
                        if (exp == 10086) { // 64位无符号!!!;不要用 -1
                            close(fd);
                            epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL);
                            close(g_eventfd);
                            epoll_ctl(epollfd, EPOLL_CTL_DEL, g_eventfd, NULL);
                            return NULL;
                        }
                    }
                }
            }
        }
    }
    close(fd);
    epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL);
    close(g_eventfd);
    epoll_ctl(epollfd, EPOLL_CTL_DEL, g_eventfd, NULL);
    return NULL;
}

int main(int argc, char *argv[]) {
    // 注册信号
    // signal(SIGINT, signal_handler); // kill -l 
    signal(SIGUSR1, signal_handler); // kill -SIGUSR1 <pid>: 使用 SIGUSR1 和 SIGUSR2 信号来进行信号传递。这两个信号是专门为用户定义的信号,可以用于进程间通信或者在同一进程的不同线程之间进行通信。

    if (argc != 4) {
        fprintf(stderr, "Usage %s init-secs interval-secs max-exp\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    g_eventfd = eventfd(0, 0);
    if (g_eventfd == -1) 
        handle_error("eventfd failed");

    g_timerfd = create_timer();
    init_timer(g_timerfd, atoi(argv[1]), 0, atoi(argv[2]), 0);
    g_exp = atoi(argv[3]);
    print_elapsed_time(); // 第一次

    pthread_t tid; 
    pthread_create(&tid, NULL, work_function, &g_timerfd);
    pthread_join(tid, NULL);
    return 0;   
}

运行示例:(1s第一次到期,每个3s定时一次,总共10次,kill杀死信号后结束程序)

在这里插入图片描述

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

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

相关文章

该死的科斯定理和三条保命原则

* * * 原创&#xff1a;刘教链 * * * 号外&#xff1a;今天在“刘教链”公众号次条发表了《内参&#xff1a;美联储下半年加息时间表和路径》&#xff0c;一号两文无法直接链接&#xff0c;请大家点击公众号卡片进入文章列表打开阅读。星球会员可以直接打开知识星球或discord …

企业级微服务架构实战项目--xx优选3-mq+nacos+es实现上下架

一 nacosmqes实现上下架 1.1 架构图 1.2 工程结构 1.3 核心代码流程 1.3.1 请求product模块 2.修改数据库&#xff0c;推送rabbitmq中 1.3.2 rabbitmq的工具类 1.3.3 search模块中rabbit客户端订阅信息 1.监听器监听信息 2.调用相应的上下架方法 2.1 调用product模块&…

c#网编实验五--WCF和TCP消息通信实验

分别编写服务端和客户端程序&#xff0c;利用基于WCF的TCP技术&#xff0c;实现在线聊天功能&#xff0c;完成在线用户列表管理&#xff0c;消息发送、接收的功能。 在同一个解决方案中&#xff0c;分别编写服务端程序和客户端程序&#xff0c;利用TCP实现简单的群聊功能。 具…

【领域驱动设计专题】一文带领你透视DDD领域驱动模型的本质和设计原理分析指南(构建领域知识)

一文带领你透视DDD领域驱动模型的本质和设计原理分析指南 前提介绍传统的软件设计方案瀑布设计方法敏捷方法学敏捷方法学的问题和局限性 构建领域知识认识和了解领域内容实体模型介绍分析飞行计划路线(route)路线(route) 领域专家进行交流&#xff0c;相互交换知识挖掘出关键的…

一台服务器最大能支持多少条 TCP 连接

一、一台服务器最大能打开的文件数 1、限制参数 我们知道在Linux中一切皆文件&#xff0c;那么一台服务器最大能打开多少个文件呢&#xff1f;Linux上能打开的最大文件数量受三个参数影响&#xff0c;分别是&#xff1a; fs.file-max &#xff08;系统级别参数&#xff09;&a…

npm发布自己的包

按照上面流程操作

基于Echarts构建停车场数据可视化大屏

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

XSS—存储型xss

xss >跨站脚本攻击>前端代码注入>用户输入的数据会被当做前端代码执行。 原理&#xff1a;使用者提交的XSS代码被存储到服务器上的数据库里或页面或某个上传文件里&#xff0c;导致用户访问页面展示的内容时直接触发xss代码。 输入内容后直接在下方回显&#xff0c;回…

【Python 随练】今天是哪一天?

题目&#xff1a; 输入某年某月某日&#xff0c;判断这一天是这一年的第几天&#xff1f; 简介&#xff1a; 在本篇博客中&#xff0c;我们将使用Python代码解决一个日期相关的问题&#xff1a;如何确定某一天是给定年份的第几天。我们将提供问题的解析&#xff0c;并给出一…

领导看了给你加薪!python +ddt+excel 一招鲜,接口自动化测试轻松搞定,测试报告惊艳四座!

“ 接口自动化测试是指通过编写代码或使用工具&#xff0c;模拟用户发送请求&#xff0c;验证接口是否符合设计规范和功能需求的过程。” 如何用 python ddtexcel 实现接口自动化测试 接口自动化测试可以提高测试效率和质量&#xff0c;节省测试成本和时间&#xff0c;保证测试…

使用PyTorch执行特征提取和微调的迁移学习来进行图像分类

使用PyTorch执行特征提取和微调的迁移学习来进行图像分类 1. 效果图2 项目结构3 什么是迁移学习4 如何使用PyTorch进行迁移学习&#xff1f;5 花朵数据集源码train_feature_extraction.pyfine_tune.pyinference.py 参考 这篇博客将介绍如何使用PyTorch深度学习库执行图像分类的…

U盘重装系统Win10详细步骤和方法

当前超多的用户都在使用Win10系统&#xff0c;有些用户想使用U盘来重装一下Win10系统&#xff0c;但不知道具体怎么操作&#xff0c;其实操作起来难度不会很大&#xff0c;可以按照以下小编给大家分享的U盘重装系统Win10详细步骤和方法&#xff0c;就能轻松顺利完成U盘重装系统…

Jetson TX2 NX的GPIO引脚使用方式

Jetson TX2 NX是一款高性能的嵌入式AI计算平台&#xff0c;其中引脚的设计和使用对于开发人员来说非常重要。在本文中&#xff0c;我们将会介绍Jetson TX2 NX的引脚并说明其功能和使用方式。 官方文档官方文档 引脚概述 Jetson TX2 NX具有许多不同类型的引脚&#xff0c;包…

C++ 类的构造函数和析构函数

目录 类的构造函数和析构函数构造函数声明构造函数定义构造函数使用构造函数默认构造函数 析构函数析构函数的声明析构函数的定义 改进Stock类(加入构造函数和析构函数) 类的构造函数和析构函数 构造函数 常规的初始化语法不适用类的初始化 例如&#xff1a; int a 10;//整…

Deepin20.9 安装Mysql

文章目录 mysql下载查看 mysql 状态卸载卸载mysql&#xff1a;清理残留数据检查是否删除完毕 mysql Deepin 安装 下载 从网上下载 https://dev.mysql.com/get/mysql-apt-config_0.8.23-1_all.deb 安装 mysql-apt-config 下载文件名: mysql-apt-config_0.8.23-1_all.deb …

PoseiSwap IDO 即将开启,一览 $POSE 经济模型

以太坊创始人 Vitalik Buterin 曾在今年以太坊黑山大会上&#xff0c;进行了以“以太坊的三个技术挑战&#xff1a;扩容、隐私和用户安全”为主题的演讲&#xff0c;阐明了具有隐私性、可扩展性和安全性的且易访问的区块链生态将是行业发展趋势&#xff0c;或许重复造轮子正在变…

【探索 Kubernetes|作业管理篇 系列 10】Pod 健康检查和恢复机制

前言 大家好&#xff0c;我是秋意零。 上一篇中介绍了&#xff0c;Pod 的服务对象&#xff0c;从而对 Pod 有了更深的理解&#xff1b; 今天的主题是 Pod 健康检查和恢复机制&#xff0c;我们将结束 Pod 的内容。 最近搞了一个扣扣群&#xff0c;旨在技术交流、博客互助&am…

图像中提取文本

将从此图像中提取文本。我使用得是 PyCharm&#xff0c;您随意编辑器或IDE 1、下载所需得库和exe文件&#xff1a; tesseract-ocr 可执行exe文件下载后&#xff0c;安装时无需指定安装目录。 http://jaist.dl.sourceforge.net/project/tesseract-ocr-alt/tesseract-ocr-setup-3…

代码随想录二刷day25 | 回溯 之 216.组合总和III 17.电话号码的字母组合

216.组合总和III 题目链接 解题思路&#xff1a; 选取过程如图&#xff1a; 图中&#xff0c;可以看出&#xff0c;只有最后取到集合&#xff08;1&#xff0c;3&#xff09;和为4 符合条件。 递归三部曲 确定递归函数参数 和77. 组合 一样&#xff0c;依然需要一维数组path…

走进人工智能|深度学习 算法的创世纪

前言&#xff1a; 深度学习通过训练深层神经网络模型&#xff0c;可以自动学习和提取数据的特征&#xff0c;包括更准确的图像识别、自然语言处理、医学诊断等方面的应用。 文章目录 序言背景算法的创世纪技术支持应用领域程序员如何学总结 序言 深度学习是一种机器学习方法&a…