当前位置: 首页 > news >正文

浅析Redis②:命令处理之epoll实现(中)

写在前面

Redis作为我们日常工作中最常使用的缓存数据库,其重要性不言而喻,作为普通开发者,我们在日常开发中使用Redis,主要聚焦于Redis的基层数据结构的命令使用,很少会有人对Redis的内部实现机制进行了解,对于我而言,也是如此,但一直以来,我对于Redis的内部实现都很好奇,它为什么会如此高效,本系列文章是旨在对Redis源代码分析拆解,通过阅读Redis源代码,了解Redis基础数据结构的实现机制。

关于Redis的源码分析,已经有非常多的大佬写过相关的内容,最为著名的是《Redis设计与实现》,对于Redis源码的分析已经非常出色,本系列文章对于源码拆解时,并不会那么详细,相信大部分读者应该不是从事Redis的二次开发工作,对于源码细节过于深入,会陷入细节的泥潭,这是我在阅读源码时尽量避免的,我尽量做到对大体的脉络进行梳理,讲清楚主干逻辑,细节部分,如果读者有兴趣,可以自行参阅源码或相关资料。

本系列源代码,基于Redis 3.2.6

前言

在上一篇中浅析Redis①:命令处理核心源码分析(上),我们大致了解了Redis客户端命令请求的处理流程,在整个流程中,我们还有两个问题没有解释:

1、非阻塞的核心epoll是如何实现的?

2、Redis是如何将数据写回Client端的?

本篇我们就围绕第一个问题,寻找答案,继续看Redis客户端命令请求的处理流程。

Redis的epoll实现

Redis的非阻塞I/O是指Redis在处理客户端请求时,不会一直等待I/O操作完成,而是会尽快返回,并在I/O操作完成后通知Redis进行后续处理。

epoll作为非阻塞I/O的实现,是Linux内核提供的一种多路I/O复用机制。epoll可以监视多个文件描述符,一旦某个文件描述符就绪,epoll就会通知Redis进行后续处理。

Redis的非阻塞I/O模型可以提高并发处理能力,在阻塞I/O模型中,Redis在处理一个客户端请求时,如果遇到I/O操作,会一直等待I/O操作完成,这意味着Redis无法处理其他客户端的请求。

而在非阻塞I/O模型中,Redis在遇到I/O操作时,会尽快返回,并在I/O操作完成后通知Redis进行后续处理。这样,Redis就可以同时处理多个客户端的请求,提高了并发处理能力。

同时非阻塞I/O模型还可以减少Redis的CPU占用率。在阻塞I/O模型中,Redis在遇到I/O操作时,会一直等待I/O操作完成,这意味着Redis的CPU会一直处于占用状态。

在非阻塞I/O模型中,Redis在遇到I/O操作时,会尽快返回,并在I/O操作完成后通知Redis进行后续处理。这样CPU就不会一直处于占用状态,可以减少CPU占用率,提升CPU使用效率。

核心实现

Redis非阻塞IO的实现是基于OS的内核函数支持,源码逻辑如下:

redis.c中main方法启动,执行initServer()初始化redis配置,同时创建非阻塞事件监听器:

server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);for (j = 0; j < server.ipfd_count; j++) {if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,acceptTcpHandler,NULL) == AE_ERR){redisPanic("Unrecoverable error creating server.ipfd file event.");}
}

其中,ipfd_count默认参数为1024,该参数表示Redis可以同时处理的最大TCP连接数。

aeCreateEventLoop与aeCreateFileEvent的实现逻辑在ae.c文件中:

aeEventLoop *aeCreateEventLoop(int setsize) {aeEventLoop *eventLoop;int i;if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err;eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;eventLoop->setsize = setsize;eventLoop->lastTime = time(NULL);eventLoop->timeEventHead = NULL;eventLoop->timeEventNextId = 0;eventLoop->stop = 0;eventLoop->maxfd = -1;eventLoop->beforesleep = NULL;if (aeApiCreate(eventLoop) == -1) goto err;/* Events with mask == AE_NONE are not set. So let's initialize the* vector with it. */for (i = 0; i < setsize; i++)eventLoop->events[i].mask = AE_NONE;return eventLoop;err:if (eventLoop) {zfree(eventLoop->events);zfree(eventLoop->fired);zfree(eventLoop);}return NULL;
}int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,aeFileProc *proc, void *clientData)
{if (fd >= eventLoop->setsize) {errno = ERANGE;return AE_ERR;}aeFileEvent *fe = &eventLoop->events[fd];if (aeApiAddEvent(eventLoop, fd, mask) == -1)return AE_ERR;fe->mask |= mask;if (mask & AE_READABLE) fe->rfileProc = proc;if (mask & AE_WRITABLE) fe->wfileProc = proc;fe->clientData = clientData;if (fd > eventLoop->maxfd)eventLoop->maxfd = fd;return AE_OK;
}

其中aeApiCreate()是核心创建逻辑,aeApiCreate()方法采用了类似Java中多态的实现方式,由于C本身并不支持多态,因此需要使用C中的技巧实现:

/* Include the best multiplexing layer supported by this system.* The following should be ordered by performances, descending. */
#ifdef HAVE_EVPORT
#include "ae_evport.c"
#else#ifdef HAVE_EPOLL#include "ae_epoll.c"#else#ifdef HAVE_KQUEUE#include "ae_kqueue.c"#else#include "ae_select.c"#endif#endif
#endif

这段代码是Redis中的一个条件编译语句,用于根据不同的操作系统和编译器选择不同的事件驱动库。

事件驱动库是Redis的一个核心组件,用于处理各种事件,包括网络IO事件、定时器事件等。Redis支持多种事件驱动库,比如epoll、kqueue、select等。在编译Redis时,需要根据操作系统和编译器选择合适的事件驱动库进行编译。

这段代码中,首先判断是否定义了HAVE_EVPORT宏。如果定义了该宏,则使用ae_evport.c文件中的事件驱动库,否则继续判断是否定义了HAVE_EPOLL宏。如果定义了该宏,则使用ae_epoll.c文件中的事件驱动库,否则继续判断是否定义了HAVE_KQUEUE宏。如果定义了该宏,则使用ae_kqueue.c文件中的事件驱动库,否则使用ae_select.c文件中的事件驱动库。

这种条件编译技术可以使Redis在不同操作系统和编译器下具有更好的兼容性和可移植性,使得Redis可以在不同的平台上运行,并且可以充分发挥不同平台的优势。

简言之,就是根据不同的操作系统,决定选择不同的内核IO模型,优先级: evport > epoll > kqueue > select

关于系统内核实现,参考:

#ifdef HAVE_EVPORT: 如果定义了宏 HAVE_EVPORT,则包含文件 ae_evport.c。ae_evport.c 可能包含了 Solaris 10 系统使用的事件驱动库。

#else: 如果没有定义宏 HAVE_EVPORT,则继续处理后续代码。

#ifdef HAVE_EPOLL: 如果定义了宏 HAVE_EPOLL,则包含文件 ae_epoll.c。ae_epoll.c 可能包含了 Linux 系统使用的事件驱动库 epoll。

#else: 如果没有定义宏 HAVE_EPOLL,则继续处理后续代码。

#ifdef HAVE_KQUEUE: 如果定义了宏 HAVE_KQUEUE,则包含文件 ae_kqueue.c。ae_kqueue.c 可能包含了 FreeBSD 或 macOS 系统使用的事件驱动库 kqueue。

#else: 如果没有定义宏 HAVE_KQUEUE,则包含文件 ae_select.c。ae_select.c 可能包含了所有系统都支持的 select 事件驱动库,但效率较低。

#endif: 结束条件编译语句块。

我们常用的CentOS使用的是epoll的实现,在Linux系统中,epoll机制是一种高效的事件触发机制,可以监听大量的文件描述符,并在文件描述符上发生事件时,立即通知应用程序。使用epoll机制时,需要使用epoll_create函数创建一个epoll对象,然后使用epoll_ctl函数向epoll对象添加或删除文件描述符,最后使用epoll_wait函数等待事件的发生。

epoll的实现在ae_epoll.c文件中:

static int aeApiCreate(aeEventLoop *eventLoop) {aeApiState *state = zmalloc(sizeof(aeApiState));if (!state) return -1;state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize);if (!state->events) {zfree(state);return -1;}state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */if (state->epfd == -1) {zfree(state->events);zfree(state);return -1;}eventLoop->apidata = state;return 0;
}static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {aeApiState *state = eventLoop->apidata;struct epoll_event ee;/* If the fd was already monitored for some event, we need a MOD* operation. Otherwise we need an ADD operation. */int op = eventLoop->events[fd].mask == AE_NONE ?EPOLL_CTL_ADD : EPOLL_CTL_MOD;ee.events = 0;mask |= eventLoop->events[fd].mask; /* Merge old events */if (mask & AE_READABLE) ee.events |= EPOLLIN;if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;ee.data.u64 = 0; /* avoid valgrind warning */ee.data.fd = fd;if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;return 0;
}

epoll_create是Linux系统中的一个系统调用,用于创建一个epoll对象,以便对文件描述符进行事件监听。

在Linux系统中,如果需要对多个文件描述符进行事件监听,常用的方式是使用select或poll函数。但是随着文件描述符数量的增加,select和poll函数的效率会逐渐降低,因为它们需要遍历所有的文件描述符,而无法实现快速的事件通知。为了解决这个问题,Linux引入了epoll机制,通过epoll_create系统调用创建一个epoll对象,然后使用epoll_ctl函数向epoll对象添加或删除文件描述符,最后使用epoll_wait函数等待事件的发生。

epoll_create函数的原型如下:

#include <sys/epoll.h>
int epoll_create(int size);

其中,size参数表示epoll对象中能够监听的最大文件描述符数量,这个参数在Linux 2.6.8之后已经无效,可以忽略。epoll_create函数返回一个整数类型的文件描述符,表示创建的epoll对象的标识符。如果创建失败,返回-1。

需要注意的是,使用epoll_create函数创建的epoll对象是在内核中创建的,而不是在用户空间中创建的。因此,在使用epoll机制时,需要将文件描述符设置为非阻塞模式,并且需要使用epoll_ctl函数向内核注册文件描述符,从而实现文件描述符的事件监听。

epoll_ctl是Linux系统中的一个系统调用,用于向epoll对象中添加或删除文件描述符,并设置对应的事件类型。

在Linux系统中,epoll机制是一种高效的事件触发机制,可以监听大量的文件描述符,并在文件描述符上发生事件时,立即通知应用程序。使用epoll机制时,需要使用epoll_create函数创建一个epoll对象,然后使用epoll_ctl函数向epoll对象添加或删除文件描述符,最后使用epoll_wait函数等待事件的发生。

epoll_ctl函数的原型如下:

#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

其中,epfd参数表示epoll对象的文件描述符,op参数表示操作类型,可以是EPOLL_CTL_ADD、EPOLL_CTL_MOD或EPOLL_CTL_DEL,分别表示添加、修改或删除文件描述符。fd参数表示要添加、修改或删除的文件描述符,event参数表示要监听的事件类型,包括读事件、写事件等。

需要注意的是,使用epoll_ctl函数添加、修改或删除文件描述符时,需要将文件描述符设置为非阻塞模式。在调用epoll_wait函数等待事件时,如果有事件发生,epoll_wait函数会返回一组事件列表,然后可以处理这些事件。处理完毕后,可以使用epoll_ctl函数修改或删除已经处理过的文件描述符,然后再次调用epoll_wait函数等待事件的发生。

epoll_wait是一个Linux内核提供的系统调用,用于等待文件描述符上的事件。epoll是Linux内核提供的一种多路I/O复用机制,可以监视多个文件描述符,一旦某个文件描述符就绪,epoll就会通知用户进程进行后续处理。

epoll_wait的函数原型如下:

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

参数说明如下:

  • epfd:epoll实例的文件描述符。
  • events:用于存放就绪文件描述符的数组。
  • maxevents:events数组的大小。
  • timeout:等待事件的超时时间,单位为毫秒。

epoll_wait的返回值如下:

  • 成功时,返回就绪文件描述符的数目。
  • 出错时,返回-1。

epoll_wait的使用步骤如下:

  1. 创建一个epoll实例,并获取其文件描述符。
  2. 将需要监视的文件描述符注册到epoll实例中。
  3. 调用epoll_wait函数,等待事件。
  4. 处理就绪文件描述符上的事件。

以下是epoll_wait的使用示例:

#include <sys/epoll.h>
int main() {// 创建一个epoll实例int epfd = epoll_create(1024);if (epfd == -1) {perror("epoll_create");return -1;}// 将需要监视的文件描述符注册到epoll实例中struct epoll_event event;event.events = EPOLLIN;event.data.fd = 0; // 标准输入if (epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event) == -1) {perror("epoll_ctl");return -1;}// 等待事件struct epoll_event events[10];int nfds = epoll_wait(epfd, events, 10, -1);if (nfds == -1) {perror("epoll_wait");return -1;}// 处理就绪文件描述符上的事件for (int i = 0; i < nfds; ++i) {if (events[i].events & EPOLLIN) {// 读取标准输入char buf[1024];int n = read(events[i].data.fd, buf, sizeof(buf));if (n == -1) {perror("read");return -1;}// ...}}return 0;
}

在上述示例中,我们创建了一个epoll实例,并将标准输入注册到epoll实例中。然后,我们调用epoll_wait函数,等待标准输入上的数据到达。如果标准输入上有数据到达,epoll_wait函数就会返回,并将就绪文件描述符的相关信息保存在events数组中。最后,我们遍历events数组,处理每个就绪文件描述符上的事件。

epoll_wait是Linux内核提供的一种高效的多路I/O复用机制。它可以提高程序的并发处理能力,减少CPU占用率。

ae_epoll.c文件中封装了一系列的epoll操作,包括epoll的创建、新增、删除、扩容、等待。

那这个非阻塞IO是怎么工作的?

核心关注aeApiPoll()

static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {aeApiState *state = eventLoop->apidata;int retval, numevents = 0;retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);if (retval > 0) {int j;numevents = retval;for (j = 0; j < numevents; j++) {int mask = 0;struct epoll_event *e = state->events+j;if (e->events & EPOLLIN) mask |= AE_READABLE;if (e->events & EPOLLOUT) mask |= AE_WRITABLE;if (e->events & EPOLLERR) mask |= AE_WRITABLE;if (e->events & EPOLLHUP) mask |= AE_WRITABLE;eventLoop->fired[j].fd = e->data.fd;eventLoop->fired[j].mask = mask;}}return numevents;
}

epoll_wait等待FD的就绪通知,如果FD准备完毕,则进行数据流处理,否则就阻塞等待,在Redis启动时,会在main函数中创建一个死循环,轮询监听epoll事件,当有事件就绪时,执行事件的回调函数,即我们上一篇中所讲到的,具体的命令执行函数。

ae.c aeMain()

void aeMain(aeEventLoop *eventLoop) {eventLoop->stop = 0;while (!eventLoop->stop) {if (eventLoop->beforesleep != NULL)eventLoop->beforesleep(eventLoop);aeProcessEvents(eventLoop, AE_ALL_EVENTS);}
}
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{int processed = 0, numevents;//省略部分非核心代码.....// 等待epoll就绪事件numevents = aeApiPoll(eventLoop, tvp);for (j = 0; j < numevents; j++) {aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];int mask = eventLoop->fired[j].mask;int fd = eventLoop->fired[j].fd;int rfired = 0;/* note the fe->mask & mask & ... code: maybe an already processed* event removed an element that fired and we still didn't* processed, so we check if the event is still valid. */// 核心:执行命令对应的回调函数if (fe->mask & mask & AE_READABLE) {rfired = 1;fe->rfileProc(eventLoop,fd,fe->clientData,mask);}// 核心:执行命令执行结果数据,写回客户端,回调函数if (fe->mask & mask & AE_WRITABLE) {if (!rfired || fe->wfileProc != fe->rfileProc)fe->wfileProc(eventLoop,fd,fe->clientData,mask);}processed++;}}/* Check time events */if (flags & AE_TIME_EVENTS)processed += processTimeEvents(eventLoop);return processed; /* return the number of processed file/time events */
}

就此,命令执行流程的epoll部分,就此完成,我们还是用一张图描述整个执行过程:
redis命令执行

结语

本篇,我们对Redis源码中非阻塞的核心epoll是如何实现进行了浅析,简单了解了Redis中epoll的工作流程,至此,我们已经大体了解了Redis如何处理执行来自客户端的命令请求,但是还有一个问题我们没有清楚,Redis是如何将命令读取到的数据返回客户端的,下一篇中,我们将围绕这个问题,进行拆解,敬请期待。

相关文章:

浅析Redis②:命令处理之epoll实现(中)

写在前面 Redis作为我们日常工作中最常使用的缓存数据库&#xff0c;其重要性不言而喻&#xff0c;作为普通开发者&#xff0c;我们在日常开发中使用Redis&#xff0c;主要聚焦于Redis的基层数据结构的命令使用&#xff0c;很少会有人对Redis的内部实现机制进行了解&#xff0c…...

react如果创建了类似于 Icketang元素,那么该如何实现 Icketang类

要实现一个类似于 "Icketang" 的类&#xff0c;首先需要考虑该类的属性和方法。根据上下文&#xff0c;可以假设 "Icketang" 是一个卡片或票据类&#xff0c;可以包含以下属性和方法&#xff1a; 属性&#xff1a; card_number&#xff1a;卡片编号amoun…...

「数字化转型」企业架构:成功业务转型的关键

在麦肯锡最近的一篇文章中&#xff0c;他们雄辩地论证了企业架构对数字转型的重要性。但他们也对实践状况提出了一些重要的批评。为了真正有效地支持数字转型&#xff0c;许多企业架构实践需要改变他们的行为。 一些EA实践首先关注的是详细记录企业的当前状态。这通常是我们在许…...

AI开启手机摄影新时代:三星Galaxy S24 Ultra影像解读

在全球科技领域&#xff0c;生成式AI无疑是当前最为炙手可热的亮点&#xff0c;不少行业专家和业界领袖都纷纷预言&#xff0c;生成式AI技术必将重塑千行百业。 那么是否有人想过&#xff0c;如果生成式AI技术被应用在智能手机上&#xff0c;又会带来怎样翻天覆地的变革&#x…...

Linux ---- Shell编程之函数与数组

目录 一、函数 1、函数的基本格式 2、查看函数列表 3、删除函数 4、函数的传参数 5、函数返回值 实验&#xff1a; 1.判断输入的ip地址正确与否 2. 判断是否为管理员用户登录 6、函数变量的作用范围 7、函数递归&#xff08;重要、难点&#xff09; 实验&#xff1…...

Python系列(9)—— 比较运算符

在Python中&#xff0c;比较运算符用于比较两个值的大小关系&#xff0c;如等于、不等于、大于、小于等。这些运算符可以帮助我们进行各种比较操作&#xff0c;并返回布尔值&#xff08;True或False&#xff09;。下面我们将详细介绍Python中的比较运算符。 等于运算符&#x…...

uni-app h5对接 thinkphp5接口跨域

uni-app h5对接 thinkphp5接口跨域 问题描述 请求接口 提示 Access to XMLHttpRequest at http://******* from origin http://localhost:8091 has been blocked by CORS policy: Response to preflight request doesnt pass access control check: It does not have HTTP o…...

react-jss书写样式

目录 react-jss的使用 react-jss的使用 实现组件化样式、动态样式、避免样式冲突 npm install react-jss yarn add react-jss// 使用 import React from react; import { createUseStyles } from react-jss;const useStyles createUseStyles({myButton: {color: green,margi…...

Oracle PL/SQL Programming 第3章:Language Fundamentals 读书笔记

总的目录和进度&#xff0c;请参见开始读 Oracle PL/SQL Programming 第6版 每种语言&#xff08;无论是人类语言还是计算机语言&#xff09;都有语法、词汇和字符集。 为了使用该语言进行交流&#xff0c;您必须学习管理其使用的规则。 我们许多人对学习新的计算机语言持谨慎…...

【Spring Boot 3】【@Scheduled】动态修改定时任务时间

【Spring Boot 3】【@Scheduled】动态修改定时任务时间 背景介绍开发环境开发步骤及源码工程目录结构总结背景 软件开发是一门实践性科学,对大多数人来说,学习一种新技术不是一开始就去深究其原理,而是先从做出一个可工作的DEMO入手。但在我个人学习和工作经历中,每次学习…...

WordPress如何自定义日期和时间格式?附PHP日期和时间格式字符串

WordPress网站在很多地方都需要用到日期和时间&#xff0c;那么我们应该在哪里设置日期和时间呢&#xff1f;又如何自定义日期和时间格式呢&#xff1f;下面boke112百科就跟大家一起来学习一下PHP标准化的日期和时间格式字符串。 特别说明&#xff1a;格式字符是标准化的&#…...

log4j2 配置入门介绍

配置 将日志请求插入到应用程序代码中需要进行大量的计划和工作。 观察表明&#xff0c;大约4%的代码专门用于日志记录。因此&#xff0c;即使是中等规模的应用程序也会在其代码中嵌入数千条日志记录语句。 考虑到它们的数量&#xff0c;必须管理这些日志语句&#xff0c;而…...

深入Pyecharts:桑基图绘制与炫酷效果实战【第38篇—python:桑基图】

文章目录 深入Pyecharts&#xff1a;桑基图绘制与炫酷效果实战桑基图简介安装 Pyecharts简单桑基图的绘制自定义桑基图的炫酷效果高级样式定制 多组数据桑基图的展示动态桑基图的绘制结合真实数据的桑基图案例导出和分享进阶应用&#xff1a;桑基图与其他图表的组合总结 深入Py…...

RBD —— 不同材质破碎

目录 Working with concrete Chipping Details Proxy geometry Constraints Working with glass Chipping Proxy geometry Constraints Resolving issues with glass fracturing Working with wood Clustering Using custom cutters Working with concrete Concr…...

MySql8的简单使用(1.模糊查询 2.group by 分组 having过滤 3.JSON字段的实践)

MySql8的简单使用&#xff08;1.模糊查询 2.group by 分组 having过滤 3.JSON字段的实践&#xff09; 一.like模糊查询、group by 分组 having 过滤 建表语句 create table student(id int PRIMARY KEY,name char(10),age int,sex char(5)); alter table student add height…...

数据监控-Prometheus/Grafana

一、数据监控Prometheus 1、什么是Prometheus Prometheus是由SoundCloud开源监控告警解决方案,从2012年开始编写代码,到2015年github上开源以来,吸引不少用户以及公司的使用。Prometheus作为新一代的开源解决方案,很多理念与Google SRE的运维之道不谋而合。 2、Promet…...

Compose | UI组件(三) | TextField() 输入框组件

文章目录 TextField() 简介TextField() 输入框例子TextField() 输入框添加装饰OutlinedTextField 边框样式输入框BasicTextField 输入框组件 总结 TextField() 简介 在 Compose 中&#xff0c;TextField() 组件表示文本输入框 ExperimentalMaterial3Api Composable fun TextF…...

组件冲突、data函数、组件通信

文章目录 1.组件的三大组成部分 - 注意点说明2.组件的样式冲突&#xff08;用 scoped 解决&#xff09;3.data是一个函数4.组件通信1.什么是组件通信&#xff1f;2.不同的组件关系 和 组件通信方案分类 5.prop详解prop 校验①类型校验②完整写法&#xff08;类型&#xff0c;非…...

【C++杂货铺】详解类和对象 [上]

博主&#xff1a;代码菌-CSDN博客 专栏&#xff1a;C杂货铺_代码菌的博客-CSDN博客 目录 &#x1f308;前言&#x1f308; &#x1f4c1; 面向对象语言的特性 &#x1f4c1; 类 &#x1f4c2; 概念 &#x1f4c2; 定义 &#x1f4c1; 访问限定符 &#x1f4c2;分类 &#x…...

Linux 驱动开发基础知识—— 驱动设计的思想(六)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的在校大学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;V…...

React第五十七节 Router中RouterProvider使用详解及注意事项

前言 在 React Router v6.4 中&#xff0c;RouterProvider 是一个核心组件&#xff0c;用于提供基于数据路由&#xff08;data routers&#xff09;的新型路由方案。 它替代了传统的 <BrowserRouter>&#xff0c;支持更强大的数据加载和操作功能&#xff08;如 loader 和…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

#Uniapp篇:chrome调试unapp适配

chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器&#xff1a;Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测

uniapp 中配置 配置manifest 文档&#xff1a;manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号&#xff1a;4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅

目录 前言 操作系统与驱动程序 是什么&#xff0c;为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中&#xff0c;我们在使用电子设备时&#xff0c;我们所输入执行的每一条指令最终大多都会作用到硬件上&#xff0c;比如下载一款软件最终会下载到硬盘上&am…...

DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态

前言 在人工智能技术飞速发展的今天&#xff0c;深度学习与大模型技术已成为推动行业变革的核心驱动力&#xff0c;而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心&#xff0c;系统性地呈现了两部深度技术著作的精华&#xff1a;…...

Axure 下拉框联动

实现选省、选完省之后选对应省份下的市区...