【服务器学习】 iomanager IO协程调度模块
iomanager IO协程调度模块
以下是从sylar服务器中学的,对其的复习;
参考资料
继承自协程调度器,封装了epoll,支持为socket fd注册读写事件回调函数
IO协程调度还解决了调度器在idle状态下忙等待导致CPU占用率高的问题。IO协程调度器使用一对管道fd来tickle调度协程,当调度器空闲时,idle协程通过epoll_wait阻塞在管道的读描述符上,等管道的可读事件。添加新任务时,tickle方法写管道,idle协程检测到管道可读后退出,调度器执行调度
学习本章内容之前必须epoll接口要非常熟悉,参考链接
IO协程调度概述
IO协程调度可以看成是增强版的协程调度。
在前面的协程调度模块中,调度器对协程的调度是无条件执行的,在调度器已经启动调度的情况下,任务一旦添加成功,就会排队等待调度器执行。调度器不支持删除调度任务,并且调度器在正常退出之前一定会执行完全部的调度任务,所以在某种程度上可以认为,把一个协程添加到调度器的任务队列,就相当于调用了协程的resume方法。
IO协程调度支持协程调度的全部功能,因为IO协程调度器是直接继承协程调度器实现的。除了协程调度,IO协程调度还增加了IO事件调度的功能,这个功能是针对描述符(一般是套接字描述符)的。IO协程调度支持为描述符注册可读和可写事件的回调函数,当描述符可读或可写时,执行对应的回调函数。(这里可以直接把回调函数等效成协程,所以这个功能被称为IO协程调度)
IO事件调度功能对服务器开发至关重要,因为服务器通常需要处理大量来自客户端的socket fd,使用IO事件调度可以将开发者从判断socket fd是否可读或可写的工作中解放出来,使得程序员只需要关心socket fd的IO操作。后续的socket api hook模块也依赖IO协程调度。
sylar IO协程调度模块设计
sylar的IO协程调度模块基于epoll实现,只支持Linux平台。对每个fd,sylar支持两类事件,一类是可读事件,对应EPOLLIN,一类是可写事件,对应EPOLLOUT,sylar的事件枚举值直接继承自epoll。
当然epoll本身除了支持了EPOLLIN和EPOLLOUT两类事件外,还支持其他事件,比如EPOLLRDHUP, EPOLLERR, EPOLLHUP等,对于这些事件,sylar的做法是将其进行归类,分别对应到EPOLLIN和EPOLLOUT中,也就是所有的事件都可以表示为可读或可写事件,甚至有的事件还可以同时表示可读及可写事件,比如EPOLLERR事件发生时,fd将同时触发可读和可写事件。
对于IO协程调度来说,每次调度都包含一个三元组信息,分别是描述符-事件类型(可读或可写)-回调函数,调度器记录全部需要调度的三元组信息,其中描述符和事件类型用于epoll_wait,回调函数用于协程调度。这个三元组信息在源码上通过FdContext结构体来存储,在执行epoll_wait时通过epoll_event的私有数据指针data.ptr来保存FdContext结构体信息。
IO协程调度器在idle时会epoll_wait所有注册的fd,如果有fd满足条件,epoll_wait返回,从私有数据中拿到fd的上下文信息,并且执行其中的回调函数。(实际是idle协程只负责收集所有已触发的fd的回调函数并将其加入调度器的任务队列,真正的执行时机是idle协程退出后,调度器在下一轮调度时执行)
与协程调度器不一样的是,IO协程调度器支持取消事件。取消事件表示不关心某个fd的某个事件了,如果某个fd的可读或可写事件都被取消了,那这个fd会从调度器的epoll_wait中删除。
实现
sylar的IO协程调度器对应IOManager,这个类直接继承自Scheduler
class IOManager : public Scheduler {
public:typedef std::shared_ptr<IOManager> ptr;typedef RWMutex RWMutexType;
...
}
读写事件的定义,这里直接继承epoll的枚举值
/*** @brief IO事件,继承自epoll对事件的定义* @details 这里只关心socket fd的读和写事件,其他epoll事件会归类到这两类事件中*/
enum Event {/// 无事件NONE = 0x0,/// 读事件(EPOLLIN)READ = 0x1,/// 写事件(EPOLLOUT)WRITE = 0x4,
};
对描述符-事件类型-回调函数三元组的定义,这个三元组也称为fd上下文,使用结构体FdContext来表示。由于fd有可读和可写两种事件,每种事件的回调函数也可以不一样,所以每个fd都需要保存两个事件类型-回调函数组合。FdContext结构体定义如下
/*** @brief socket fd上下文类* @details 每个socket fd都对应一个FdContext,包括fd的值,fd上的事件,以及fd的读写事件上下文*/
struct FdContext {typedef Mutex MutexType;/*** @brief 事件上下文类* @details fd的每个事件都有一个事件上下文,保存这个事件的回调函数以及执行回调函数的调度器* sylar对fd事件做了简化,只预留了读事件和写事件,所有的事件都被归类到这两类事件中*/struct EventContext {/// 执行事件回调的调度器Scheduler *scheduler = nullptr;/// 事件回调协程Fiber::ptr fiber;/// 事件回调函数std::function<void()> cb;};/*** @brief 获取事件上下文类* @param[in] event 事件类型* @return 返回对应事件的上下文*/EventContext &getEventContext(Event event);/*** @brief 重置事件上下文* @param[in, out] ctx 待重置的事件上下文对象*/void resetEventContext(EventContext &ctx);/*** @brief 触发事件* @details 根据事件类型调用对应上下文结构中的调度器去调度回调协程或回调函数* @param[in] event 事件类型*/void triggerEvent(Event event);/// 读事件上下文EventContext read;/// 写事件上下文EventContext write;/// 事件关联的句柄int fd = 0;/// 该fd添加了哪些事件的回调函数,或者说该fd关心哪些事件Event events = NONE;/// 事件的MutexMutexType mutex;
};
IOManager的成员变量。IOManager包含一个epoll实例的句柄m_epfd以及用于tickle的一对pipe fd,还有全部的fd上下文m_fdContexts
/// epoll 文件句柄
int m_epfd = 0;
/// pipe 文件句柄,fd[0]读端,fd[1]写端
int m_tickleFds[2];
/// 当前等待执行的IO事件数量
std::atomic<size_t> m_pendingEventCount = {0};
/// IOManager的Mutex
RWMutexType m_mutex;
/// socket事件上下文的容器
std::vector<FdContext *> m_fdContexts;
在继承类IOManager中改造协程调度器,使其支持epoll,并重载tickle和idle,实现通知调度协程和IO协程调度功能
/*** @brief 构造函数* @param[in] threads 线程数量* @param[in] use_caller 是否将调用线程包含进去* @param[in] name 调度器的名称*/
IOManager::IOManager(size_t threads, bool use_caller, const std::string &name): Scheduler(threads, use_caller, name) {// 创建epoll实例m_epfd = epoll_create(5000);SYLAR_ASSERT(m_epfd > 0);// 创建pipe,获取m_tickleFds[2],其中m_tickleFds[0]是管道的读端,m_tickleFds[1]是管道的写端int rt = pipe(m_tickleFds);SYLAR_ASSERT(!rt);// 注册pipe读句柄的可读事件,用于tickle调度协程,通过epoll_event.data.fd保存描述符epoll_event event;memset(&event, 0, sizeof(epoll_event));event.events = EPOLLIN | EPOLLET;event.data.fd = m_tickleFds[0];// 非阻塞方式,配合边缘触发rt = fcntl(m_tickleFds[0], F_SETFL, O_NONBLOCK);SYLAR_ASSERT(!rt);// 将管道的读描述符加入epoll多路复用,如果管道可读,idle中的epoll_wait会返回rt = epoll_ctl(m_epfd, EPOLL_CTL_ADD, m_tickleFds[0], &event);SYLAR_ASSERT(!rt);contextResize(32);// 这里直接开启了Schedluer,也就是说IOManager创建即可调度协程start();
}/*** @brief 通知调度器有任务要调度* @details 写pipe让idle协程从epoll_wait退出,待idle协程yield之后Scheduler::run就可以调度其他任务* 如果当前没有空闲调度线程,那就没必要发通知*/
void IOManager::tickle() {SYLAR_LOG_DEBUG(g_logger) << "tickle";if(!hasIdleThreads()) {return;}int rt = write(m_tickleFds[1], "T", 1);SYLAR_ASSERT(rt == 1);
}/*** @brief idle协程* @details 对于IO协程调度来说,应阻塞在等待IO事件上,idle退出的时机是epoll_wait返回,对应的操作是tickle或注册的IO事件就绪* 调度器无调度任务时会阻塞idle协程上,对IO调度器而言,idle状态应该关注两件事,一是有没有新的调度任务,对应Schduler::schedule(),* 如果有新的调度任务,那应该立即退出idle状态,并执行对应的任务;二是关注当前注册的所有IO事件有没有触发,如果有触发,那么应该执行* IO事件对应的回调函数*/
void IOManager::idle() {SYLAR_LOG_DEBUG(g_logger) << "idle";// 一次epoll_wait最多检测256个就绪事件,如果就绪事件超过了这个数,那么会在下轮epoll_wati继续处理const uint64_t MAX_EVNETS = 256;epoll_event *events = new epoll_event[MAX_EVNETS]();std::shared_ptr<epoll_event> shared_events(events, [](epoll_event *ptr) {delete[] ptr;});while (true) {if(stopping()) {SYLAR_LOG_DEBUG(g_logger) << "name=" << getName() << "idle stopping exit";break;}// 阻塞在epoll_wait上,等待事件发生static const int MAX_TIMEOUT = 5000;int rt = epoll_wait(m_epfd, events, MAX_EVNETS, MAX_TIMEOUT);if(rt < 0) {if(errno == EINTR) {continue;}SYLAR_LOG_ERROR(g_logger) << "epoll_wait(" << m_epfd << ") (rt="<< rt << ") (errno=" << errno << ") (errstr:" << strerror(errno) << ")";break;}// 遍历所有发生的事件,根据epoll_event的私有指针找到对应的FdContext,进行事件处理for (int i = 0; i < rt; ++i) {epoll_event &event = events[i];if (event.data.fd == m_tickleFds[0]) {// ticklefd[0]用于通知协程调度,这时只需要把管道里的内容读完即可,本轮idle结束Scheduler::run会重新执行协程调度uint8_t dummy[256];while (read(m_tickleFds[0], dummy, sizeof(dummy)) > 0);continue;}// 通过epoll_event的私有指针获取FdContextFdContext *fd_ctx = (FdContext *)event.data.ptr;FdContext::MutexType::Lock lock(fd_ctx->mutex);/*** EPOLLERR: 出错,比如写读端已经关闭的pipe* EPOLLHUP: 套接字对端关闭* 出现这两种事件,应该同时触发fd的读和写事件,否则有可能出现注册的事件永远执行不到的情况*/if (event.events & (EPOLLERR | EPOLLHUP)) {event.events |= (EPOLLIN | EPOLLOUT) & fd_ctx->events;}int real_events = NONE;if (event.events & EPOLLIN) {real_events |= READ;}if (event.events & EPOLLOUT) {real_events |= WRITE;}if ((fd_ctx->events & real_events) == NONE) {continue;}// 剔除已经发生的事件,将剩下的事件重新加入epoll_wait,// 如果剩下的事件为0,表示这个fd已经不需要关注了,直接从epoll中删除int left_events = (fd_ctx->events & ~real_events);int op = left_events ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;event.events = EPOLLET | left_events;int rt2 = epoll_ctl(m_epfd, op, fd_ctx->fd, &event);if (rt2) {SYLAR_LOG_ERROR(g_logger) << "epoll_ctl(" << m_epfd << ", "<< (EpollCtlOp)op << ", " << fd_ctx->fd << ", " << (EPOLL_EVENTS)event.events << "):"<< rt2 << " (" << errno << ") (" << strerror(errno) << ")";continue;}// 处理已经发生的事件,也就是让调度器调度指定的函数或协程if (real_events & READ) {fd_ctx->triggerEvent(READ);--m_pendingEventCount;}if (real_events & WRITE) {fd_ctx->triggerEvent(WRITE);--m_pendingEventCount;}} // end for/*** 一旦处理完所有的事件,idle协程yield,这样可以让调度协程(Scheduler::run)重新检查是否有新任务要调度* 上面triggerEvent实际也只是把对应的fiber重新加入调度,要执行的话还要等idle协程退出*/Fiber::ptr cur = Fiber::GetThis();auto raw_ptr = cur.get();cur.reset();raw_ptr->yield();} // end while(true)
}
注册事件回调addEvent,删除事件回调delEvent,取消事件回调cancelEvent,以及取消全部事件cancelAll
/*** @brief 添加事件* @details fd描述符发生了event事件时执行cb函数* @param[in] fd socket句柄* @param[in] event 事件类型* @param[in] cb 事件回调函数,如果为空,则默认把当前协程作为回调执行体* @return 添加成功返回0,失败返回-1*/
int IOManager::addEvent(int fd, Event event, std::function<void()> cb) {// 找到fd对应的FdContext,如果不存在,那就分配一个FdContext *fd_ctx = nullptr;RWMutexType::ReadLock lock(m_mutex);if ((int)m_fdContexts.size() > fd) {fd_ctx = m_fdContexts[fd];lock.unlock();} else {lock.unlock();RWMutexType::WriteLock lock2(m_mutex);contextResize(fd * 1.5);fd_ctx = m_fdContexts[fd];}// 同一个fd不允许重复添加相同的事件FdContext::MutexType::Lock lock2(fd_ctx->mutex);if (SYLAR_UNLIKELY(fd_ctx->events & event)) {SYLAR_LOG_ERROR(g_logger) << "addEvent assert fd=" << fd<< " event=" << (EPOLL_EVENTS)event<< " fd_ctx.event=" << (EPOLL_EVENTS)fd_ctx->events;SYLAR_ASSERT(!(fd_ctx->events & event));}// 将新的事件加入epoll_wait,使用epoll_event的私有指针存储FdContext的位置int op = fd_ctx->events ? EPOLL_CTL_MOD : EPOLL_CTL_ADD;epoll_event epevent;epevent.events = EPOLLET | fd_ctx->events | event;epevent.data.ptr = fd_ctx;int rt = epoll_ctl(m_epfd, op, fd, &epevent);if (rt) {SYLAR_LOG_ERROR(g_logger) << "epoll_ctl(" << m_epfd << ", "<< (EpollCtlOp)op << ", " << fd << ", " << (EPOLL_EVENTS)epevent.events << "):"<< rt << " (" << errno << ") (" << strerror(errno) << ") fd_ctx->events="<< (EPOLL_EVENTS)fd_ctx->events;return -1;}// 待执行IO事件数加1++m_pendingEventCount;// 找到这个fd的event事件对应的EventContext,对其中的scheduler, cb, fiber进行赋值fd_ctx->events = (Event)(fd_ctx->events | event);FdContext::EventContext &event_ctx = fd_ctx->getEventContext(event);SYLAR_ASSERT(!event_ctx.scheduler && !event_ctx.fiber && !event_ctx.cb);// 赋值scheduler和回调函数,如果回调函数为空,则把当前协程当成回调执行体event_ctx.scheduler = Scheduler::GetThis();if (cb) {event_ctx.cb.swap(cb);} else {event_ctx.fiber = Fiber::GetThis();SYLAR_ASSERT2(event_ctx.fiber->getState() == Fiber::RUNNING, "state=" << event_ctx.fiber->getState());}return 0;
}
/*** @brief 删除事件* @param[in] fd socket句柄* @param[in] event 事件类型* @attention 不会触发事件* @return 是否删除成功*/
bool IOManager::delEvent(int fd, Event event) {// 找到fd对应的FdContextRWMutexType::ReadLock lock(m_mutex);if ((int)m_fdContexts.size() <= fd) {return false;}FdContext *fd_ctx = m_fdContexts[fd];lock.unlock();FdContext::MutexType::Lock lock2(fd_ctx->mutex);if (SYLAR_UNLIKELY(!(fd_ctx->events & event))) {return false;}// 清除指定的事件,表示不关心这个事件了,如果清除之后结果为0,则从epoll_wait中删除该文件描述符Event new_events = (Event)(fd_ctx->events & ~event);int op = new_events ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;epoll_event epevent;epevent.events = EPOLLET | new_events;epevent.data.ptr = fd_ctx;int rt = epoll_ctl(m_epfd, op, fd, &epevent);if (rt) {SYLAR_LOG_ERROR(g_logger) << "epoll_ctl(" << m_epfd << ", "<< (EpollCtlOp)op << ", " << fd << ", " << (EPOLL_EVENTS)epevent.events << "):"<< rt << " (" << errno << ") (" << strerror(errno) << ")";return false;}// 待执行事件数减1--m_pendingEventCount;// 重置该fd对应的event事件上下文fd_ctx->events = new_events;FdContext::EventContext &event_ctx = fd_ctx->getEventContext(event);fd_ctx->resetEventContext(event_ctx);return true;
}/*** @brief 取消事件* @param[in] fd socket句柄* @param[in] event 事件类型* @attention 如果该事件被注册过回调,那就触发一次回调事件* @return 是否删除成功*/
bool IOManager::cancelEvent(int fd, Event event) {// 找到fd对应的FdContextRWMutexType::ReadLock lock(m_mutex);if ((int)m_fdContexts.size() <= fd) {return false;}FdContext *fd_ctx = m_fdContexts[fd];lock.unlock();FdContext::MutexType::Lock lock2(fd_ctx->mutex);if (SYLAR_UNLIKELY(!(fd_ctx->events & event))) {return false;}// 删除事件Event new_events = (Event)(fd_ctx->events & ~event);int op = new_events ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;epoll_event epevent;epevent.events = EPOLLET | new_events;epevent.data.ptr = fd_ctx;int rt = epoll_ctl(m_epfd, op, fd, &epevent);if (rt) {SYLAR_LOG_ERROR(g_logger) << "epoll_ctl(" << m_epfd << ", "<< (EpollCtlOp)op << ", " << fd << ", " << (EPOLL_EVENTS)epevent.events << "):"<< rt << " (" << errno << ") (" << strerror(errno) << ")";return false;}// 删除之前触发一次事件fd_ctx->triggerEvent(event);// 活跃事件数减1--m_pendingEventCount;return true;
}/*** @brief 取消所有事件* @details 所有被注册的回调事件在cancel之前都会被执行一次* @param[in] fd socket句柄* @return 是否删除成功*/
bool IOManager::cancelAll(int fd) {// 找到fd对应的FdContextRWMutexType::ReadLock lock(m_mutex);if ((int)m_fdContexts.size() <= fd) {return false;}FdContext *fd_ctx = m_fdContexts[fd];lock.unlock();FdContext::MutexType::Lock lock2(fd_ctx->mutex);if (!fd_ctx->events) {return false;}// 删除全部事件int op = EPOLL_CTL_DEL;epoll_event epevent;epevent.events = 0;epevent.data.ptr = fd_ctx;int rt = epoll_ctl(m_epfd, op, fd, &epevent);if (rt) {SYLAR_LOG_ERROR(g_logger) << "epoll_ctl(" << m_epfd << ", "<< (EpollCtlOp)op << ", " << fd << ", " << (EPOLL_EVENTS)epevent.events << "):"<< rt << " (" << errno << ") (" << strerror(errno) << ")";return false;}// 触发全部已注册的事件if (fd_ctx->events & READ) {fd_ctx->triggerEvent(READ);--m_pendingEventCount;}if (fd_ctx->events & WRITE) {fd_ctx->triggerEvent(WRITE);--m_pendingEventCount;}SYLAR_ASSERT(fd_ctx->events == 0);return true;
}
IOManager的析构函数实现和stopping重载。对于IOManager的析构,首先要等Scheduler调度完所有的任务,然后再关闭epoll句柄和pipe句柄,然后释放所有的FdContext;对于stopping,IOManager在判断是否可退出时,还要加上所有IO事件都完成调度的条件
IOManager::~IOManager() {stop();close(m_epfd);close(m_tickleFds[0]);close(m_tickleFds[1]);for (size_t i = 0; i < m_fdContexts.size(); ++i) {if (m_fdContexts[i]) {delete m_fdContexts[i];}}
}
bool IOManager::stopping() {// 对于IOManager而言,必须等所有待调度的IO事件都执行完了才可以退出return m_pendingEventCount == 0 && Scheduler::stopping();
}
相关文章:
【服务器学习】 iomanager IO协程调度模块
iomanager IO协程调度模块 以下是从sylar服务器中学的,对其的复习; 参考资料 继承自协程调度器,封装了epoll,支持为socket fd注册读写事件回调函数 IO协程调度还解决了调度器在idle状态下忙等待导致CPU占用率高的问题。IO协程调…...
前端设计模式之【迭代器模式】
文章目录 前言介绍实现接口优缺点应用场景后言 前言 hello world欢迎来到前端的新世界 😜当前文章系列专栏:前端设计模式 🐱👓博主在前端领域还有很多知识和技术需要掌握,正在不断努力填补技术短板。(如果出现错误&a…...
Linux-用户与用户组,权限
1.用户组管理(以下命令需root用户执行) ①创建用户组 groupadd 用户组名 ②删除用户组 groupdel 用户组名 2.用户管理(以下命令需root用户执行) ①创建用户 useradd [-g -d] 用户名 >-g:指定用户的组,不…...
使用nvm-windows在Windows下轻松管理多个Node.js版本
Node.js是一个非常流行的JavaScript运行时环境,许多开发者在开发过程中可能需要在不同的Node.js版本之间进行切换。在Windows操作系统下,我们可以使用nvm-windows来轻松管理多个Node.js版本。本文将详细介绍如何安装和使用nvm-windows。 什么是nvm-wind…...
2023.11.10 hadoop,hive框架概念,基础组件
目录 分布式和集群的概念: hadoop架构的三大组件:Hdfs,MapReduce,Yarn 1.hdfs 分布式文件存储系统 Hadoop Distributed File System 2.MapReduce 分布式计算框架 3.Yarn 资源调度管理框架 三个组件的依赖关系是: hive数据仓库处理工具 hive的大体流程: Apache hive的…...
Kubernetes 创建pod的yaml文件-简单版-nginx
apiVersion: v1 #api文档版本 kind: Pod # 资源类型 Deployment,StatefulSet之类 metadata: #pod元数据 描述信息 name: nginx-demo labels: type: app #自定义标签 version: 1.0.0 # 自定义pod版本 namespace: default spec: #期望Pod按照这里的描述创建 cont…...
Git的进阶操作,在idea中部署gie
🏅我是默,一个在CSDN分享笔记的博主。📚📚 🌟在这里,我要推荐给大家我的专栏《git》。🎯🎯 🚀无论你是编程小白,还是有一定基础的程序员,这…...
设计模式-迭代器模式(Iterator)
设计模式-迭代器模式(Iterator) 一、迭代器模式概述1.1 什么是迭代器模式1.2 简单实现迭代器模式1.3 使用迭代器模式的注意事项 二、迭代器模式的用途三、迭代器模式实现方式3.1 使用Iterator接口实现迭代器模式3.2 使用Iterable接口和Iterator接口实现迭…...
【计算机网络笔记】Internet网络的网络层——IP协议之IP数据报的结构
系列文章目录 什么是计算机网络? 什么是网络协议? 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能(1)——速率、带宽、延迟 计算机网络性能(2)…...
【Git】Git的GUI图形化工具ssh协议IDEA集成Git
一、GIT的GUI图形化工具 1、介绍 Git自带的GUI工具,主界面中各个按钮的意思基本与界面文字一致,与git的命令差别不大。在了解自己所做的操作情况下,各个功能点开看下就知道是怎么操作的。即使不了解,只要不做push操作,…...
Java中抽象类
1 抽象方法必须包含在抽象类中 package charactor; public abstract class Hero { String name; float hp;float armor;int moveSpeed;public static void main(String[] args) {}// 抽象方法attack // Hero的子类会被要求实现attack方法 public abstract void attack();} …...
18 Linux 阻塞和非阻塞 IO
一、阻塞和非阻塞 IO 1. 阻塞和非阻塞简介 这里的 IO 指 Input/Output(输入/输出),是应用程序对驱动设备的输入/输出操作。当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将对应应用…...
多因素验证如何让企业邮箱系统登录更安全?
企业邮箱系统作为基础的办公软件之一,既是企业内外沟通的重要工具,也是连接企业多个办公平台的桥梁,往往涉及到客户隐私、业务信息、企业机密等等。为了保护邮箱账户的安全,设置登陆密码无疑是保护账户安全的常用措施之一。然而随…...
投票助手图文音视频礼物打赏流量主小程序开源版开发
投票助手图文音视频礼物打赏流量主小程序开源版开发 图文投票:用户可以发布图文投票,选择相应的选项进行投票。 音视频投票:用户可以发布音视频投票,观看音视频后选择相应的选项进行投票。 礼物打赏:用户可以在投票过…...
黑客(网络安全)技术——高效自学1.0
前言 前几天发布了一篇 网络安全(黑客)自学 没想到收到了许多人的私信想要学习网安黑客技术!却不知道从哪里开始学起!怎么学 今天给大家分享一下,很多人上来就说想学习黑客,但是连方向都没搞清楚就开始学习…...
8255 boot介绍及bring up经验分享
这篇文章会简单的介绍8255的启动流程,然后着重介绍8255在实际项目中新硬件上的bring up工作,可以给大家做些参考。 8255 boot介绍 下面这些信息来自文档:《QAM8255P IVI Boot and CoreBSP Architecture Technical Overview》 80-42847-11 R…...
visual studio 启用DPI识别功能
在开发widow程序时,有时必须将电脑 设置-->显示-->缩放与布局-->更改文本、应用项目的大小-->100%后,程序的画面才能正确运行,居说这是锁定了dpi的原因,需要启dpi识别功能。设置方法如下: 或者...
一题三解(暴力、二分查找算法、单指针):鸡蛋掉落
涉及知识点 暴力、二分查找算法、单指针 题目 给你 k 枚相同的鸡蛋,并可以使用一栋从第 1 层到第 n 层共有 n 层楼的建筑。 已知存在楼层 f ,满足 0 < f < n ,任何从 高于 f 的楼层落下的鸡蛋都会碎,从 f 楼层或比它低的…...
第一章 Object-XML 映射简介
文章目录 第一章 Object-XML 映射简介基础如何工作的映射选项IRIS 中的相关工具XML 文档的可能应用 第一章 Object-XML 映射简介 基础 将对象映射到 XML 一词意味着定义如何将该对象用作 XML 文档。要将对象映射到 XML,请将 %XML.Adaptor 添加到定义该对象的类的超…...
精密设备企业适合哪款CRM客户管理体系?
精密设备企业致力于打造现代化管理体系,以精密的仪器、精细的销售、精准的市场、精确的售后为企业核心,提供优质的精密产品和专业服务。随着企业的发展及市场发展需要,建立高效的客户关系管理体系势在必行。那么,精密设备企业适合…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...
DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...
深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
