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

io多路复用, select, poll, epoll

系列文章目录

异步I/O操作函数aio_xxx函数 https://blog.csdn.net/surfaceyan/article/details/134710393


文章目录

  • 系列文章目录
  • 前言
  • 一、5种IO模型
  • 二、IO多路复用API
    • select
    • poll
    • epoll
  • 三、两种高效的事件处理模式
    • Reactor模式
    • Proactor模式
    • 模拟 Proactor 模式
      • 基于事件驱动的非阻塞同步IO
      • 辅助函数
  • 四、多种线程池的实现方式
    • 基本的
    • modern C++
  • references


前言


一、5种IO模型

  1. 阻塞IO
  2. 非阻塞IO
  3. IO复用
  4. 信号驱动
    Linux用套接字进行信号驱动IO,安装一个信号处理函数,进程继续运行并不阻塞,当IO事件就绪,进程收到SIGIO信号,然后处理IO事件
  5. 异步
    https://blog.csdn.net/surfaceyan/article/details/134710393

二、IO多路复用API

include

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/select.h>
#include <poll.h>
#include <sys/epoll.h>

select

selelct 能够监控的最大文件描述符数量必须小于FD_SETSIZE,poll和epoll没有文件描述符数量限制
select返回后所有的参数都看成未定义的需要重填

 int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);- nfds: 监听集合中最大的文件描述符+1,数组索引+1- readfds: 监听待读取的文件描述符集合- writefds: 监听待写入的文件描述符集合- exceptfds: 监听“exceptional conditions”,see POLLPRI in poll(2)
当函数返回后,上面的集合都会被清零,除了集合中满足条件的
- timeout: 超时时间, 为NULL, select阻塞,时间为0则函数立即返回函数会在以下情况时返回:- 一个文件描述符处于就绪状态- 调用被信号句柄中断- 时间到期select调用后可将timeout看成未定义的(timeout剩余时间,有些系统可能不会这样做)
select调用后返回 r w e集合总共被置位的个数,0代表到期
-1代表错误,可能原因: badfd, signal int, nfds < 0 > RLIMIT_NOFILE, timeout invalid, 内存不足导致无法分配内部表格
int pselect(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, const struct timespec *timeout,const sigset_t *sigmask);
pselect允许捕获信号
select可能更新timeout为剩余时间,pselect不会改变这个参数
int main()
{fd_set rfds;struct timeval tv;int retval;FD_ZERO(&rfds);int fd = 0;FD_SET(fd, &rfds);tv.tv_sec = 5;tv.tv_usec = 0;retval = select(fd+1, &rfds, NULL, NULL, &tv);if (retval < 0)perror("select()");else if (retval) {printf("data is %d.\n", FD_ISSET(fd, &rfds);char buf[BUFSIZ] = {0};int n = read(fd, buf, BUFSIZ);printf("n %d: %s\n", n, buf);} else {printf("timeout\n");}
}

poll

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- fds: 待监控的fd集合struct pollfd {int fd;  // 如果< 0 则events会被忽略,revents返回0short events;   // requested events  输入参数short revents;  // returned events  输出参数};
- nfds: fds数组大小
- timeout: 阻塞的毫秒数,-1代表阻塞,0代表不阻塞
epoll会一直阻塞,直到:- 一个文件描述符准备就绪- 调用被信号中断- 时间到期POLLIN: 就绪读
POLLPRI: 异常条件
POLLOUT: 可写的
POLLRDHUP: socket对端关闭了连接
POLLERR:必有
POLLHUP: 必有,对端关闭链接后再read返回0(EOF)(仅当所有残留的数据都被读取时)
POLLNVAL:必有,无效请求(fd没有open)成功返回非负值,指明pollfds中有几个revents为非零,0代表时间到期
-1 on errorEFAULT fds指针错误EINTR  被信号中断EINVAL The nfds value exceeds the RLIMIT_NOFILE value.EINVAL (ppoll()) The timeout value expressed in *ip is invalid (negative).ENOMEM 不能为内核数据结构分配内存
 int ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p, const sigset_t *sigmask);
允许应用程序安全地等待,直到文件描述符准备就绪或捕获到信号。
#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \} while (0)int main(int argc, char *argv[])
{int nfds, num_open_fds;struct pollfd *pfds;if (argc < 2) {fprintf(stderr, "Usage: %s file...\n", argv[0]);exit(EXIT_FAILURE);}num_open_fds = nfds = argc - 1;pfds = (pollfd*)calloc(nfds, sizeof(struct pollfd));if (pfds == NULL)errExit("malloc");/* Open each file on command line, and add it 'pfds' array */for (int j = 0; j < nfds; j++) {pfds[j].fd = open(argv[j + 1], O_RDONLY);if (pfds[j].fd == -1)errExit("open");printf("Opened \"%s\" on fd %d\n", argv[j + 1], pfds[j].fd);pfds[j].events = POLLIN;}/* Keep calling poll() as long as at least one file descriptor isopen */while (num_open_fds > 0) {int ready;printf("About to poll()\n");ready = poll(pfds, nfds, -1);if (ready == -1)errExit("poll");printf("Ready: %d\n", ready);/* Deal with array returned by poll() */for (int j = 0; j < nfds; j++) {char buf[10];if (pfds[j].revents != 0) {printf("  fd=%d; events: %s%s%s\n", pfds[j].fd,(pfds[j].revents & POLLIN)  ? "POLLIN "  : "",(pfds[j].revents & POLLHUP) ? "POLLHUP " : "",(pfds[j].revents & POLLERR) ? "POLLERR " : "");if (pfds[j].revents & POLLIN) {ssize_t s = read(pfds[j].fd, buf, sizeof(buf));if (s == -1)errExit("read");printf("    read %zd bytes: %.*s\n",s, (int) s, buf);} else {                /* POLLERR | POLLHUP */printf("    closing fd %d\n", pfds[j].fd);if (close(pfds[j].fd) == -1)errExit("close");num_open_fds--;}}}}printf("All file descriptors closed; bye\n");exit(EXIT_SUCCESS);
}

epoll

epoll_create(2), epoll_create1(2), epoll_ctl(2), epoll_wait(2)

int epoll_create1(int flags);
创建一个epoll实例
- flags: 为0 等价于epoll_createEPOLL_CLOEXEC
           typedef union epoll_data {void    *ptr;int      fd;uint32_t u32;uint64_t u64;} epoll_data_t;struct epoll_event {uint32_t     events;    /* Epoll events */epoll_data_t data;      /* User data variable */};
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events,int maxevents, int timeout,const sigset_t *sigmask);
;
返回就绪的问题件描述符个数
0代表时间到期
-1代表 EINTR被信号中断
 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);- op: - EPOLL_CTL_ADD: - EPOLL_CTL_MOD- EPOLL_CTL_DELevents为带监听的事件
- EPOLLIN :
- EPOLLOUT :
- EPOLLRDHUP :对端关闭连接(√)
- EPOLLPRI :异常条件(√)
- EPOLLERR :发生错误(默认必监听)(√)
- EPOLLHUP : 类似对端关闭链接(默认必监听)(√)
- EPOLLET :
- EPOLLONESHOT : 只会触发一次
- EPOLLWAKEUP :
- EPOLLEXCLUSIVE: (默认必监听)(√)
#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
epollfd = epoll_create1(EPOLL_CLOEXEC);
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev);
for (;;)
{nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);if (nfds == -1) {error;}for (int i = 0; i < nfds; ++n) {if (events[n].data.fd == listen_sock) {con_sock = accept(listen_sock, NULL, NULL);setnonblocking(conn_sock);ev.data.fd  conn_sock;ev.events = EPOLLIN | EPOLLET;epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev);} else {do_use_fd(events[i].data.fd);}}
}

三、两种高效的事件处理模式

服务器程序通常需要处理三类事件:I/O 事件、信号及定时事件。有两种高效的事件处理模式:Reactor
和 Proactor,同步 I/O 模型通常用于实现 Reactor 模式,异步 I/O 模型通常用于实现 Proactor 模式。

Reactor模式

要求主线程(I/O处理单元)只负责监听文件描述符上是否有事件发生,有的话就立即将该事件通知工作
线程(逻辑单元),将 socket 可读可写事件放入请求队列,交给工作线程处理。除此之外,主线程不做
任何其他实质性的工作。读写数据,接受新的连接,以及处理客户请求均在工作线程中完成。

使用同步 I/O(以 epoll_wait 为例)实现的 Reactor 模式的工作流程是:

  1. 主线程往 epoll 内核事件表中注册 socket 上的读就绪事件。
  2. 主线程调用 epoll_wait 等待 socket 上有数据可读。
  3. 当 socket 上有数据可读时, epoll_wait 通知主线程。主线程则将 socket 可读事件放入请求队列。
  4. .睡眠在请求队列上的某个工作线程被唤醒,它从 socket 读取数据,并处理客户请求,然后往 epoll
    内核事件表中注册该 socket 上的写就绪事件。
  5. .当主线程调用 epoll_wait 等待 socket 可写。
  6. 当 socket 可写时,epoll_wait 通知主线程。主线程将 socket 可写事件放入请求队列。
  7. 睡眠在请求队列上的某个工作线程被唤醒,它往 socket 上写入服务器处理客户请求的结果。

在这里插入图片描述

Proactor模式

Proactor 模式将所有 I/O 操作都交给主线程和内核来处理(进行读、写),工作线程仅仅负责业务逻
辑。使用异步 I/O 模型(以 aio_read 和 aio_write 为例)实现的 Proactor 模式的工作流程是:

  1. 主线程调用 aio_read 函数向内核注册 socket 上的读完成事件,并告诉内核用户读缓冲区的位置,以及读操作完成时如何通知应用程序(这里以信号为例)。
  2. 主线程继续处理其他逻辑。
  3. 当 socket 上的数据被读入用户缓冲区后,内核将向应用程序发送一个信号,以通知应用程序数据已经可用。
  4. 应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求后,调用 aio_write 函数向内核注册 socket 上的写完成事件,并告诉内核用户写缓冲区的位置,以及写操作完成时如何通知应用程序。
  5. 主线程继续处理其他逻辑。
  6. 当用户缓冲区的数据被写入 socket 之后,内核将向应用程序发送一个信号,以通知应用程序数据已经发送完毕。
  7. 应用程序预先定义好的信号处理函数选择一个工作线程来做善后处理,比如决定是否关闭 socket。

在这里插入图片描述

模拟 Proactor 模式

使用同步 I/O 方式模拟出 Proactor 模式。原理是:主线程执行数据读写操作,读写完成之后,主线程向工作线程通知这一”完成事件“。那么从工作线程的角度来看,它们就直接获得了数据读写的结果,接下来要做的只是对读写的结果进行逻辑处理。
使用同步 I/O 模型(以 epoll_wait为例)模拟出的 Proactor 模式的工作流程如下:

  1. . 主线程往 epoll 内核事件表中注册 socket 上的读就绪事件。
  2. 主线程调用 epoll_wait 等待 socket 上有数据可读。
  3. 当 socket 上有数据可读时,epoll_wait 通知主线程。主线程从 socket 循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列。
  4. 睡眠在请求队列上的某个工作线程被唤醒,它获得请求对象并处理客户请求,然后往 epoll 内核事件表中注册 socket 上的写就绪事件。
  5. 主线程调用 epoll_wait 等待 socket 可写。
  6. . 主线程调用 epoll_wait 等待 socket 可写。
  7. 当 socket 可写时,epoll_wait 通知主线程。主线程往 socket 上写入服务器处理客户请求的结果。

请添加图片描述

基于事件驱动的非阻塞同步IO

int main(int argc, char* argv[])
{Client* clients = new Client[MAXIMUM_FD];int listen_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);struct sockaddr_in address;address.sin_addr.s_addr = INADDR_ANY;address.sin_family = AF_INET;address.sin_port = htons( std::atoi(argv[1]) );int reuse = 1;int ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));assert(ret == 0);ret = bind(listen_fd, (struct sockaddr*)&address, sizeof(address));assert(ret == 0);ret = listen(listen_fd, 5);assert(ret == 0);epoll_event events[MAX_EVENTS];int epoll_fd = epoll_create1(EPOLL_CLOEXEC);assert(epoll_fd > 0);addfd2epoll(epoll_fd, listen_fd, false);Client::listen_fd = listen_fd;Client::epoll_fd = epoll_fd;while (1)  {int number = epoll_pwait(epoll_fd, events, MAX_EVENTS, -1, nullptr);if (number < 0) {if (errno == EINTR) {fprintf(stderr, "interrupted by a sig\n");break;} else {perror("epoll_wait");break;}} else if (number == 0){continue;  // timeout}for (int i = 0; i < number; i++) {int sockfd = events[i].data.fd;if (sockfd == listen_fd) {int client_fd = accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);if (client_fd < 0) continue;if (client_fd >= MAXIMUM_FD) {fprintf(stderr, "clients out of limits\n");close(client_fd);continue;}clients[client_fd].init(client_fd, NULL);} else if (events[i].events & (EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP)) {clients[sockfd].close_connection();  // error} else if (events[i].events & EPOLLIN) {Client* client = clients + sockfd;if ( client->do_read() && pool->push_back(client)) {continue;}client->close_connection();} else if (events[i].events & EPOLLOUT) {Client* client = clients + sockfd;if (client->do_write() == false) {client->close_connection();}}}}close(epoll_fd);close(listen_fd);delete[] clients;delete pool;return 0;
}

bool Client::do_read()
{if (recv_idx_ >= recv_buf_len_) return false;while (true) {int n_read = recv(client_fd_, recv_buf_+recv_idx_, grecv_buf_len-recv_idx_, 0);if (n_read < 0) {if (errno == EAGAIN || errno == EWOULDBLOCK) break;  // read donereturn false;} else if (n_read == 0) { // closed by peerreturn false;}recv_idx_ += n_read;}return true;
}

bool Client::do_write()
{if (send_num_ >= send_idx_) {init();modfd2epoll(epoll_fd, client_fd_, EPOLLIN);return true;}while (1) {int n_send = send(client_fd_, send_buf_+send_num_, send_idx_-send_num_, 0);if (n_send < 0) {if (errno == EAGAIN || errno == EWOULDBLOCK) {modfd2epoll(epoll_fd, client_fd_, EPOLLOUT);return true;}return false;} else if (n_send == 0) {  // 对端已经关闭,然后继续写会收到 SIGPIPEfprintf(stderr, "client: %d\n n_send = 0\n", client_fd_);return false;}send_num_ += n_send;if (send_num_ >= send_idx_) {init();modfd2epoll(epoll_fd, client_fd_, EPOLLIN);return true;}}
}

辅助函数

int setnonblocking(int fd)
{int old_opt = fcntl(fd, F_GETFL);int new_opt = old_opt | O_NONBLOCK;fcntl(fd, F_SETFL, new_opt);return old_opt;
}
int setblocking(int fd)
{int old_opt = fcntl(fd, F_GETFL);int new_opt = old_opt & (~O_NONBLOCK);fcntl(fd, F_SETFL, new_opt);return old_opt;
}
int setcloexec(int fd)
{int old_opt = fcntl(fd, F_GETFD);int new_opt = old_opt | FD_CLOEXEC;fcntl(fd, F_SETFD, new_opt);return old_opt;
} 
void addfd2epoll(int epoll, int fd, bool one_shot)
{epoll_event event;event.data.fd = fd;event.events = EPOLLIN | EPOLLRDHUP | EPOLLPRI;if (one_shot){event.events |= EPOLLONESHOT;}epoll_ctl(epoll, EPOLL_CTL_ADD, fd, &event);
}
void rmfromepoll(int epoll, int fd)
{epoll_ctl(epoll, EPOLL_CTL_DEL, fd, nullptr);
}
void modfd2epoll(int epoll, int fd, int ev)
{epoll_event event;event.data.fd = fd;event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP | EPOLLPRI;epoll_ctl(epoll, EPOLL_CTL_MOD, fd, &event);
}

四、多种线程池的实现方式

基本的

#include <pthread.h>
#include <semaphore.h>
#include <list>
#include <exception>
class ThpoolException : public std::exception
{
public:const char* what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW override{ return "thread pool init failed\n"; }
};
class LockGuard  // RAII
{
private:pthread_mutex_t& mtx_;
public:LockGuard(pthread_mutex_t& mtx) : mtx_(mtx){ pthread_mutex_lock(&mtx_); }~LockGuard(){ pthread_mutex_unlock(&mtx_); }
};template<typename T>
class thread_pool
{
public:static bool running;
private:int thread_num;std::list<T*> queue;int max_q_len;pthread_mutex_t q_mtx;sem_t q_sem;
public:thread_pool(int thread_num_, int max_len) : thread_num(thread_num_), max_q_len(max_len){int ret = 0;ret = pthread_mutex_init(&q_mtx, nullptr);if (ret != 0) throw ThpoolException();ret = sem_init(&q_sem, 0, 0);if (ret != 0) throw ThpoolException();for (int i=0; i < thread_num; ++i) {pthread_t thid;ret = pthread_create(&thid, nullptr, &thread_pool::on_process, this);if (ret != 0) throw ThpoolException();ret = pthread_detach(thid);if (ret != 0) throw ThpoolException();}}~thread_pool(){pthread_mutex_destroy(&q_mtx);sem_destroy(&q_sem);running = false;}bool push_back(T* client){bool success;{    LockGuard lg(q_mtx);if (queue.size() < max_q_len) {queue.push_back(client);success = true;sem_post(&q_sem);}else success = false;}return success;}static void* on_process(void* arg){ ((thread_pool*)arg)->do_process();  return nullptr; }void do_process(){while (running){T* client = nullptr;sem_wait(&q_sem);{LockGuard lg(q_mtx);if (queue.empty() == false){client = queue.front();queue.pop_front();}}if (client) client->do_process();}}
};
template<typename T>
bool thread_pool<T>::running = true;

modern C++

class TaskQueue {
public:TaskQueue() = default;virtual ~TaskQueue() = default;virtual bool enqueue(std::function<void()> fn) = 0;virtual void shutdown() = 0;virtual void on_idle() {}
};
class ThreadPool final : public TaskQueue {
public:explicit ThreadPool(size_t n, size_t mqr = 0): shutdown_(false), max_queued_requests_(mqr) {while (n) {threads_.emplace_back(worker(*this));n--;}}ThreadPool(const ThreadPool &) = delete;~ThreadPool() override = default;bool enqueue(std::function<void()> fn) override {{std::unique_lock<std::mutex> lock(mutex_);if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) {return false;}jobs_.push_back(std::move(fn));}cond_.notify_one();return true;}void shutdown() override {// Stop all worker threads...{std::unique_lock<std::mutex> lock(mutex_);shutdown_ = true;}cond_.notify_all();// Join...for (auto &t : threads_) {t.join();}}private:struct worker {explicit worker(ThreadPool &pool) : pool_(pool) {}void operator()() {for (;;) {std::function<void()> fn;{std::unique_lock<std::mutex> lock(pool_.mutex_);pool_.cond_.wait(lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; });if (pool_.shutdown_ && pool_.jobs_.empty()) { break; }fn = pool_.jobs_.front();pool_.jobs_.pop_front();}assert(true == static_cast<bool>(fn));fn();}}ThreadPool &pool_;};friend struct worker;std::vector<std::thread> threads_;std::list<std::function<void()>> jobs_;bool shutdown_;size_t max_queued_requests_ = 0;std::condition_variable cond_;std::mutex mutex_;
};

references

IO多路复用 https://www.cnblogs.com/flashsun/p/14591563.html
socket 网络编程——端口复用技术 https://blog.csdn.net/JMW1407/article/details/107321853

相关文章:

io多路复用, select, poll, epoll

系列文章目录 异步I/O操作函数aio_xxx函数 https://blog.csdn.net/surfaceyan/article/details/134710393 文章目录 系列文章目录前言一、5种IO模型二、IO多路复用APIselectpollepoll 三、两种高效的事件处理模式Reactor模式Proactor模式模拟 Proactor 模式基于事件驱动的非阻…...

k8s-1.28.2 部署prometheus

一、prometheus helm仓库 ## 网站地址 # https://artifacthub.io/## prometheus 地址 # https://artifacthub.io/packages/helm/prometheus-community/prometheus. # helm repo add prometheus-community https://prometheus-community.github.io/helm-charts # helm repo …...

记录第一次跑YOLOV8做目标检测

今天是24年的最后一天&#xff0c;终于要向新世界开始破门了&#xff0c;开始深度学习&#xff0c;YOLO来敲门~ 最近做了一些皮肤检测的功能&#xff0c;在传统的处理中经历了反复挣扎&#xff0c;终于要上YOLO了。听过、看过&#xff0c;不如上手体会过~ 1、YOLO是什么&#x…...

使用Python爬取BOSS直聘职位数据并保存到Excel

使用Python爬取BOSS直聘职位数据并保存到Excel 在数据分析和挖掘中&#xff0c;爬取招聘网站数据是一项常见的任务。本文将详细介绍如何使用Python爬取BOSS直聘上与“测试工程师”相关的职位数据&#xff0c;并将其保存到Excel文件中。通过逐步分解代码和添加详细注释&#xf…...

node.js之---集群(Cluster)模块

为什么会有集群&#xff08;Cluster&#xff09;模块&#xff1f; 集群&#xff08;Cluster&#xff09;模块的作用 如何使用集群&#xff08;Cluster&#xff09;模块&#xff1f; 为什么会有集群&#xff08;Cluster&#xff09;模块 Node.js 是基于 单线程事件驱动 模型的…...

SSM-Spring-IOC/DI对应的配置开发

目录 一、IOC 控制反转 1.什么是控制反转呢 2. Spring和IOC之间的关系是什么呢? 3.IOC容器的作用以及内部存放的是什么? 4.当IOC容器中创建好service和dao对象后&#xff0c;程序能正确执行么? 5.Spring 容器管理什么内容&#xff1f; 6.如何将需要管理的对象交给 …...

一文大白话讲清楚CSS元素的水平居中和垂直居中

文章目录 一文大白话讲清楚CSS元素的水平居中和垂直居中1.已知元素宽高的居中方案1.1 利用定位margin:auto1.2 利用定位margin负值1.3 table布局 2.未知元素宽高的居中方案2.1利用定位transform2.2 flex弹性布局2.3 grid网格布局 3. 内联元素的居中布局 一文大白话讲清楚CSS元素…...

航顺芯片推出HK32A040方案,赋能汽车矩阵大灯安全与智能化升级

汽车安全行驶对整车照明系统的要求正在向智能化方向发展。车灯位于汽车两侧&#xff0c;前期有各种各样的实现包括氙气灯、LED灯等等光源技术。矩阵大灯对汽车照明系统朝着安全性和智能化兼具的方向发展起到了重要推动作用。矩阵大灯可以精细控制到每一个小灯珠&#xff0c;从而…...

智能工厂的设计软件 应用场景的一个例子:为AI聊天工具添加一个知识系统 之12 方案再探:特定于领域的模板 之2 首次尝试和遗留问题解决

本文提要 现在就剩下“体”本身的 约定了--这必然是 自律自省的&#xff0c;或者称为“戒律” --即“体”的自我训导discipline。完整表述为&#xff1a; 严格双相的庄严“相” (<head>侧&#xff09;&#xff0c;完全双性的本质“性”&#xff08;<boot>侧&…...

redis zset底层实现

1.Redis zset底层实现 转载自&#xff1a;https://marticles.github.io/2019/03/19/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Redis-Zset%E5%8E%9F%E7%90%86/ zset底层是压缩列表 跳表实现的。 跳表里面又由字典hash表 跳表实现。 什么时候用压缩列表&#xff1f;什么时候用…...

go.Bar如何让hovertext显示为legend

在 Plotly 的 go.Bar 图中&#xff0c;如果你想让鼠标悬停时 (hover) 显示的文本 (hovertext) 与图例 (legend) 一致&#xff0c;可以通过 hovertemplate 来控制悬停时的显示内容。 实现方法 hovertemplate 是一种自定义工具&#xff0c;允许你完全控制悬停时的文本显示格式。…...

【Vue】分享一个快速入门的前端框架以及如何搭建

先上效果图: 登录 菜单: 下载地址: 链接&#xff1a;https://pan.baidu.com/s/1m-ZlBARWU6_2n8jZil_RAQ 提取码&#xff1a;ui20 … 主要是可以自定义设置token,更改后端请求地址较为方便。 应用设置: 登录与token设置: 在这里设置不用登录,可以请求的接口: request.js i…...

Flink源码解析之:如何根据JobGraph生成ExecutionGraph

Flink源码解析之&#xff1a;如何根据JobGraph生成ExecutionGraph 在上一篇Flink源码解析中&#xff0c;我们介绍了Flink如何根据StreamGraph生成JobGraph的流程&#xff0c;并着重分析了其算子链的合并过程和JobGraph的构造流程。 对于StreamGraph和JobGraph的生成来说&…...

UE(虚幻)学习(三) UnrealSharp插件中调用非托管DLL

上一篇文章中我使用UnrealSharp成功使用了我的一个C#控制台程序中的网络模块&#xff0c;这个程序是基于KCP网络了&#xff0c;其中调用了Cmake 编译的一个C的DLL&#xff0c;在虚幻中DLL需要放在Binaries目录中才可以。Unity中只要放在任意Plugins目录中就可以。 但是Binaries…...

leetcode 3219. 切蛋糕的最小总开销 II

题目&#xff1a;3219. 切蛋糕的最小总开销 II - 力扣&#xff08;LeetCode&#xff09; 排序贪心。 开销越大的越早切。 注意m或n为1的情况。 class Solution { public:long long minimumCost(int m, int n, vector<int>& horizontalCut, vector<int>&…...

vant 地址记录

vant ui 的官网地址记录 vant 4 Vant 4 - A lightweight, customizable Vue UI library for mobile web apps. vant2 https://vant-ui.github.io/vant/v2/ vant3 Vant 3 - Lightweight Mobile UI Components built on Vue...

Lua语言入门 - Lua常量

在Lua中&#xff0c;虽然没有直接的常量关键字&#xff08;如C中的const&#xff09;&#xff0c;但你可以通过一些编程技巧和约定来实现类似常量的行为。以下是几种常见的方法&#xff1a; 1. 使用全局变量并命名规范 你可以定义一个全局变量&#xff0c;并通过命名约定来表示…...

在Microsoft Windows上安装MySQL

MySQL仅适用于Microsoft Windows 64位操作系统&#xff0c;在Microsoft Windows上安装MySQL有不同的方法&#xff1a;MSI、包含您解压缩的所有必要文件的标准二进制版本&#xff08;打包为压缩文件&#xff09;以及自己编译MySQL源文件。 注意&#xff1a;MySQL8.4服务器需要在…...

windows下vscode使用msvc编译器出现中文乱码

文章目录 [toc]1、概述2、修改已创建文件编码3、修改vscode默认编码 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;C &#x1f448;&#x1f449;开发工具 &#x1f448; 1、概述 在使用MSVC编译器时&#xff0c;出现中文报错的问题可能与编码格式有关。UTF-…...

Git 解决 everything up-to-date

首先使用git log查看历史提交&#xff0c;找到最新一次提交&#xff0c;比如&#xff1a; PS D:\Unity Projects\CoffeeHouse\CoffeeHouse_BurstDebugInformation_DoNotShip> git log commit a1b54c309ade7c07c3981d3ed748b0ffac2759a3 (HEAD -> master, origin/master)…...

超短脉冲激光自聚焦效应

前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应&#xff0c;这是一种非线性光学现象&#xff0c;主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场&#xff0c;对材料产生非线性响应&#xff0c;可能…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

Admin.Net中的消息通信SignalR解释

定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

什么是EULA和DPA

文章目录 EULA&#xff08;End User License Agreement&#xff09;DPA&#xff08;Data Protection Agreement&#xff09;一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA&#xff08;End User License Agreement&#xff09; 定义&#xff1a; EULA即…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

为什么要创建 Vue 实例

核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...

【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)

LeetCode 3309. 连接二进制表示可形成的最大数值&#xff08;中等&#xff09; 题目描述解题思路Java代码 题目描述 题目链接&#xff1a;LeetCode 3309. 连接二进制表示可形成的最大数值&#xff08;中等&#xff09; 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...