Linux网络编程(三)IO复用三 epoll系统调用

news2024/11/17 5:23:20

三、epoll系统调用

epoll是Linux特有的I/O复用函数。它在实现和使用上与selectpoll有很大差异。

  • epoll使用一组函数来完成任务,而不是单个函数
  • epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无须像select和poll那样每次调用都要重复传入文件描述符集或事件集
  • epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表

3.1、内核事件表

3.1.1、创建内核事件表
#include <sys/epoll.h>

int epoll_create(int size)
  • size参数现在并不起作用,只是给内核一个提示,告诉它事件表需要多大。该函数返回的文件描述符将用作其他所有epoll系统调用的第一个参数,以指定要访问的内核事件表。
3.1.2、操作内核事件表
#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event)
  • fd参数是要操作的文件描述符;

  • op参数则指定操作类型;

    • EPOLL_CTL_ADD,往事件表中注册fd上的事件
    • EPOLL_CTL_MOD,修改fd上的注册事件
    • EPOLL_CTL_DEL,删除fd上的注册事件
  • event参数指定事件,结构如下:

    •   struct epoll_event {
          __uint32_t events; /*epoll事件*/
          epoll_data_t data; /*用户数据*/
        };
      
    • events描述事件类型,epoll支持的事件类型和poll基本相同。表示epoll事件类型的宏是在poll对应的宏前加上“E”。epoll有两个额外的事件类型——EPOLLETEPOLLONESHOT

    • data成员用于存储用户数据,结构如下:

    •   typedef union epoll_data_t {
            void* ptr;
            int fd;
            uint32_t u32;
            uint64_t u64;
        } epoll_data_t;
      
      • epoll_data_t是一个联合体,其4个成员中使用最多的是fd

      • fd 指定事件所从属的目标文件描述符

      • ptr可用来指定与fd相关的用户数据

      • 但由于epoll_data_t是一个联合体,我们不能同时使用其ptr成员和fd成员,因此,如果要将文件描述符和用户数据关联起来,只能使用其他手段,比如放弃使用fd,而在ptr指向的用户数据中包含fd。

  • epoll_ctl成功时返回0,失败则返回-1并设置errno。

3.2、epoll_wait函数

epoll系列系统调用的主要接口是epoll_wait函数。它在一段超时时间内等待一组文件描述符上的事件:

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
  • maxevents指定最大监听事件个数

  • timeout指定超时时间

  • 成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno。

epoll_wait函数如果检测到事件,就将所有就绪的事件从内核事件表(由epfd参数指定)中复制到它的第二个参数events指向的数组中。这个数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数那样既用于传入用户注册的事件,又用于输出内核检测到的就绪事件。这就极大地提高了应用程序索引就绪文件描述符的效率。

poll和epoll的区别

/*如何索引poll返回的就绪文件描述符*/
int ret = poll(fds, MAX_EVENT_NUMBER, -1);
/*必须遍历所有已注册文件描述符并找到其中的就绪者(当然,可以利用ret来稍做优化)*/
for(int i = 0; i < MAX_EVENT_NUMBER; ++i) {
	if(fds[i].revents&POLLIN) {/*判断第i个文件描述符是否就绪*/
    	int sockfd=fds[i].fd;
   		/*处理sockfd*/
    }
}

/*如何索引epoll返回的就绪文件描述符*/
int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER,-1);
/*仅遍历就绪的ret个文件描述符*/
for(int i=0; i < ret; i++) {
	int sockfd = events[i].data.fd;
	/*sockfd肯定就绪,直接处理*/
}

3.3、LT和ET模式

epoll对文件描述符的操作有两种模式:LT(Level Trigger,电平触发)模式和ET(Edge Trigger,边沿触发)模式。LT模式是默认的工作模式,这种模式下epoll相当于一个效率较高的poll。当往epoll内核事件表中注册一个文件描述符上的EPOLLET事件时,epoll将以ET模式来操作该文件描述符。ET模式是epoll的高效工作模式。

采用LT工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到该事件被处理。

采用ET工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。ET模式降低了同一个epoll事件被重复触发的次数,因此效率变高。

下面的代码解释了LT模式和ET模式的区别

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <stdbool.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>


#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 1024

/*将文件描述符设置成非阻塞的*/
int setnonblocking(int fd) {
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

/*将文件描述符fd上的EPOLLIN注册到epollfd指示的epoll内核事件表中,参数enable_et指定是否对fd启用ET模式*/
void addfd(int epollfd, int fd, bool enable_et) {
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN;
    if (enable_et) {
        event.events |= EPOLLET;
    }
    // 向事件表中注册事件
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    // 将文件描述符fd设置为非阻塞
    setnonblocking(fd);
}

/*LT模式的工作流程*/
void lt(struct epoll_event *events, int number, int epollfd, int listenfd) {
    // 缓存
    char buf[BUFFER_SIZE];
    // 遍历事件发生的描述符
    for (int i = 0; i<number; i++) {
        // 获取事件发生的文件描述符
        int sockfd = events[i].data.fd;
        // 如果是监听socket发生了事件,那么连接这个tcp,并将其加入到事件表中
        if (sockfd == listenfd) {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof(client_address);
            int connfd = accept(listenfd, (struct sockaddr *)&client_address,&client_addrlength);
            // 使用LT模式
            addfd(epollfd, connfd, false);
        }
        // 如果是有数据输入,即socket可读
        else if (events[i].events & EPOLLIN) {
            printf("event trigger once\n");
            memset(buf, '\0', BUFFER_SIZE);
            int ret = recv(sockfd, buf, BUFFER_SIZE - 1, 0);
            if (ret <= 0) {
                close(sockfd);
                continue;
            }
            printf("get%d bytes of content:%s\n", ret, buf);
        }
        // 如果是其他情况
        else {
            printf("something else happened\n");
        }
    }
}

/*ET模式的工作流程*/
void et(struct epoll_event *events, int number, int epollfd, int listenfd) {
    // 准备缓存
    char buf[BUFFER_SIZE];
    // 遍历发生事件的文件描述符
    for (int i = 0; i<number; i++){
        int sockfd = events[i].data.fd;
        // 如果是监听socket发生事件,那么连接这个tcp
        if (sockfd == listenfd){
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof(client_address);
            int connfd = accept(listenfd, (struct sockaddr*) &client_address, &client_addrlength );
            // 使用ET模式
            addfd(epollfd, connfd, true);
        }
        // 如果是有数据输入,即socket可读。请注意,这段代码不会被重复触发,所以必须一次性读取出所有的数据
        else if (events[i].events & EPOLLIN){
            printf("event trigger once\n");
            while (1){
                memset(buf, '\0', BUFFER_SIZE);
                int ret = recv(sockfd, buf, BUFFER_SIZE - 1, 0);
                if (ret<0){
                    // 对于非阻塞IO,下面的条件成立表示数据已经全部读取完毕。此后,epoll就能再次触发sockfd上的EPOLLIN事件,以驱动下一次读操作
                    if ((errno == EAGAIN) || (errno == EWOULDBLOCK)){
                        printf("read later\n");
                        break;
                    }
                    close(sockfd);
                    break;
                }
                else if (ret == 0){
                    close(sockfd);
                }
                else {
                    printf("get%d bytes of content:%s\n", ret, buf);
                }
            }
        }
        else {
            printf("something else happened\n");
        }
    }
}

int main(int argc, char *argv[]) {
    if (argc <= 2) {
        printf("usage:%s ip_address port_number\n", basename(argv[0]));
        return 1;
    }

    const char *ip = argv[1];
    int port = atoi(argv[2]);
    int ret = 0;
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip,&address.sin_addr);
    address.sin_port = htons(port);

    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenfd >= 0);

    ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(listenfd, 5);
    assert(ret != -1);

    struct epoll_event events[MAX_EVENT_NUMBER];
    // 内核事件表的大小为5
    int epollfd = epoll_create(5);
    assert(epollfd != -1);
    // listenfd 也加入内核事件表的监听中
    addfd(epollfd, listenfd, true);

    while (1) {
        // 函数成功时返回就绪文件描述符的个数,events数组中存储了就绪的事件,timeout指定为-1,永远阻塞在这里等待事件发生
        int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
        // epoll失败。
        if (ret<0) {
            printf("epoll failure\n");
            break;
        }
        lt(events, ret, epollfd, listenfd); /*使用LT模式*/
        // et(events,ret,epollfd,listenfd); /*使用ET模式*/
    }

    close(listenfd);
    return 0;
}

3.4、EPOLLONESHOT事件

即使我们使用ET模式,一个socket上的某个事件还是可能被触发多次。这在并发程序中就会引起一个问题。比如一个线程(或进程,下同)在读取完某个socket上的数据后开始处理这些数据,而在数据的处理过程中该socket上又有新数据可读(EPOLLIN再次被触发),此时另外一个线程被唤醒来读取这些新的数据。于是就出现了两个线程同时操作一个socket的局面。这当然不是我们期望的。我们期望的是一个socket连接在任一时刻都只被一个线程处理。这一点可以使用epoll的EPOLLONESHOT事件实现。

对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次,除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。这样,当一个线程在处理某个socket时,其他线程是不可能有机会操作该socket的。但反过来思考,注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这个socket。

下面的代码使用了EPOLLONESHOT事件

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <stdbool.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define PORT 8080

#define BUFFER_SIZE 1024
#define MAX_EVENT_NUMBER 1024
#define MAX_CONNECTIONS 10

struct fds {
    int epollfd;
    int sockfd;
};

int setnonblocking(int fd) {
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

/* 将fd上的EPOLLIN和EPOLLET事件注册到epollfd指示的epoll内核事件表中,参数oneshot指定是否注册fd上的EPOLLONESHOT事件*/
void addfd(int epollfd, int fd, bool oneshot) {
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    if (oneshot) {
        event.events |= EPOLLONESHOT;
    }
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    setnonblocking(fd);
}

/* 重置fd上的事件。这样操作之后,尽管fd上的EPOLLONESHOT事件被注册,但是操作系统仍然会触发fd上的EPOLLIN事件,且只触发一次*/
void reset_oneshot(int epollfd, int fd)
{
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}

/*工作线程*/
void *worker(void *arg) {
    int sockfd = ((struct fds *)arg)->sockfd;
    int epollfd = ((struct fds *)arg)->epollfd;
    printf("start new thread to receive data on fd: %d\n", sockfd);
    char buf[BUFFER_SIZE];
    memset(buf, 0, BUFFER_SIZE);
    // 循环读取sockfd上的数据,直到遇到EAGAIN错误
    while (1) {
        int ret = recv(sockfd, buf, BUFFER_SIZE - 1, 0);
        // 对方关闭了tcp连接,那么我们也关闭连接。
        if (ret == 0) {
            close(sockfd);
            printf("foreiner closed the connection\n");
            break;
        }
        // 读取数据失败,可能是资源暂不可用,信号中断,缓冲地址无效等情况
        else if (ret < 0) {
            // 如果资源暂时不可用,也可以说无数据可读,那么重置 socket 上的事件
            // 让其他线程也有机会处理这个socket上的数据
            if (errno == EAGAIN) {
                reset_oneshot(epollfd, sockfd);
                printf("read later\n");
                break;
            }
        }
        else {
            printf("get content:%s\n", buf);
            /****************************
             * 处理数据
            ****************************/
           sleep(1);
        }
    }
    printf("end thread receiving data on fd:%d\n", sockfd);
}

int main(int argc, char *argv[]) {
    int listenfd;

    // 创建 socket 文件描述符
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 绑定到指定端口
    struct sockaddr_in address;
    address.sin_family = AF_INET;   
    address.sin_port = htons(PORT);
    address.sin_addr.s_addr = inet_addr("192.168.189.129");
    int addrlen = sizeof(address);
    if (bind(listenfd, (struct sockaddr *) &address, addrlen) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 开始监听
    if (listen(listenfd, MAX_CONNECTIONS) < 0) {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    // 创建内核事件表
    struct epoll_event events[MAX_EVENT_NUMBER];
    int epollfd = epoll_create(5);
    assert(epollfd != -1);
    // 注意,监听socket listenfd上是不能注册EPOLLONESHOT事件的,否则应用程序只能处理一个客户连接!
    // 因为后续的客户连接请求将不再触发listenfd上的EPOLLIN事件
    addfd(epollfd, listenfd, false);

    // 等待事件发生
    while (1) {
        int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
        // epoll失败
        if (ret < 0) {
            printf("epoll failure\n");
            break;
        }
        // 处理发生的事件
        for (int i = 0; i < ret; i++) {
            // 获得发生事件的文件描述符
            int sockfd = events[i].data.fd;
            // 如果是监听描述符发生了事件,即有新的tcp请求,那么建立这个TCP连接,并注册为ET、EPOLLONESHOT事件
            if (sockfd == listenfd) {
                struct sockaddr_in client_address;
                socklen_t client_addrlength = sizeof(client_address);
                int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlength);
                addfd(epollfd, connfd, true);
            }
            // 如果是一个TCP连接有数据输入,那么新建立一个线程,处理这个TCP连接
            else if (events[i].events & EPOLLIN) {
                pthread_t thread;
                struct fds fds_for_new_worker;
                fds_for_new_worker.epollfd = epollfd;
                fds_for_new_worker.sockfd = sockfd;
                pthread_create(&thread, NULL, worker, (void *)&fds_for_new_worker);
            }
            // 如果是其他事件
            else {
                printf("something else happened\n");
            }
        }
    }
    close(listenfd);
    return 0;
}

直接看worker工作线程,如果工作线程处理完某个socket上的一次请求之后,又接收到该socket上新的客户请求,则该线程将继续为这个socket服务。因为这个socket注册了EPOLLONESHOT事件,其他线程无法接触到这个socket。如果当前工作线程处理完后,这个socket并没有关闭,也没有新的数据,那么当前线程放弃这个socket,等待新的事件将他触发。

四、三个IO复用函数的区别

selectpollepoll都可以同时监听多个文件描述符。它们将等待由timeout参数指定的超时时间,直到一个或者多个文件描述符上有事件发生时返回,返回值是就绪的文件描述符的数量。返回0表示没有事件发生。

我们从事件集、最大支持文件描述符数、工作模式和具体实现等四个方面进一步比较它们的异同,以明确在实际应用中应该选择使用哪个。

请添加图片描述

4.1、最大监听数量

poll和epoll_wait分别用nfds和maxevents参数指定最多监听多少个文件描述符和事件。这两个数值都能达到系统允许打开的最大文件描述符数目,即65 535(cat/proc/sys/fs/file-max)。而select允许监听的最大文件描述符数量通常有限制。虽然用户可以修改这个限制,但这可能导致不可预期的后果。

4.2、工作模式

selectpoll都只能工作在相对低效的LT模式,而epoll则可以工作在ET高效模式。并且epoll还支持EPOLLONESHOT事件。该事件能进一步减少可读、可写和异常等事件被触发的次数。

4.3、原理

selectpoll采用的都是轮询的方式,即每次调用都要扫描整个注册文件描述符集合,并将其中就绪的文件描述符返回给用户程序,因此它们检测就绪事件的算法的时间复杂度是O(n)。epoll则采用的是回调的方式。内核检测到就绪的文件描述符时,将触发回调函数,回调函数就将该文件描述符上对应的事件插入内核就绪事件队列。内核最后在适当的时机将该就绪事件队列中的内容拷贝到用户空间。算法复杂度为O(1)。

但是,当活动连接比较多的时候,epoll_wait的效率未必比select和poll高,因为此时回调函数被触发得过于频繁。所以epoll_wait适用于连接数量多,但活动连接较少的情况。

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

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

相关文章

JS笔试手撕题

数据劫持 Vue2的Object.defineProperty() Vue2的响应式是通过Object.defineProperty()拦截数据&#xff0c;将数据转换成getter/setter的形式&#xff0c;在访问数据的时候调用getter函数&#xff0c;在修改数据的时候调用setter函数。然后利用发布-订阅模式&#xff0c;在数…

计算机专业,求你别再玩了,我都替你们着急

明确学习目标和方向&#xff1a;确定自己希望在计算机领域的哪个方向深入发展&#xff0c;如前端开发、后端开发、数据库管理、人工智能等。根据目标方向&#xff0c;制定详细的学习计划&#xff0c;确保所学知识与未来职业方向相匹配。 【PDF学习资料文末获取】 扎实基础知识…

effective python学习笔记_推导与生成

用推导取代map和filter 序列推导可取代map和filter&#xff0c;优越性有&#xff1a;1可读性强2不需要map的函数 控制推导逻辑的子表达式不要超过2个 即推导的for层数最多建议两层&#xff0c;多了可读性会下降&#xff0c;反而用for循环会清晰 一层for内可连接多个if&…

为什么要使用大模型

随着OpenAI引领的超大模型风潮&#xff0c;大模型的发展日新月异&#xff0c;如同雨后春笋般茁壮成长。在现今的科技舞台上&#xff0c;每周&#xff0c;甚至每一天&#xff0c;我们都能见证到一个全新模型的开源&#xff0c;这些模型的创新性和实用性不断超越前作&#xff0c;…

激光雷达技术:科技之眼,照亮前行

在科技与人文关怀的交响乐章中&#xff0c;一项名为“蝙蝠避障”使用了激光雷达技术原理及应用的创新成果&#xff0c;正悄然改变着视障朋友们的生活方式&#xff0c;为他们的日常出行铺设了一条充满希望的光明之路。今天&#xff0c;让我们一起深入探讨这项技术如何成为盲人出…

关于Java Chassis 3的契约优先(API First)开发

契约优先&#xff08;API First&#xff09;开发是指应用程序开发过程中&#xff0c;将API设计作为第一优先级的任务。契约优先开发随着Web Services概念的发展而不断得到重视&#xff0c;特别是微服务架构出现以后&#xff0c;API设计成为影响功能开放、性能优化等问题的关键因…

企业外贸邮箱有哪些?国内五大外贸邮箱排行榜

外贸公司在进行跨国业务的时候&#xff0c;需要一个稳定安全的企业邮箱。国内的企业外贸邮箱提供商有很多&#xff0c;目前排行在前五的有Zoho Mail企业邮箱、阿里企业邮箱、网易企业邮箱、腾讯企业邮箱、新浪企业邮箱&#xff0c;今天我们就来详细了解下这些邮箱产品。 一、Z…

球形帐篷:低碳环保的未来多功能建筑—轻空间

球形帐篷是一种创新的建筑形式&#xff0c;以其环保、可持续的特点&#xff0c;正在逐渐成为未来多功能建筑的新趋势。通过采用气膜技术和轻量化材料&#xff0c;球形帐篷将为观众带来与众不同的观影、展览等体验&#xff0c;同时彰显了科技创新与环保共生的理念。 创新科技与环…

VTK数据的读写--Vtk学习记录1--《VTK图形图像开发进阶》

读和写操作是VTK可视化管线两端相关的类--Reader和Writer类 Reader:将外部数据读入可视化管线&#xff0c;主要步骤如下 s1:实例化Reader对象 s2:指定所要读取的文件名 s3:调用Update()促使管线执行 对应的Writer: s1:实例化Writer对象 s2输入要写的数据以及指定写入的文…

N个行业看板组态数据可视化大屏,海量模板库不用代码拖拉就行

芯软云设备管理大数据平台。 芯软云设备管理大数据平台&#xff0c;提供MES工艺模板、能源管理模板、智慧水务模板、智慧农业模板、实际产量、设备管理模板、布局模板等。用户可以选择自己喜欢并适合的模板进行场景构建。平台还提供除模板外&#xff0c;共五大类场景资源&…

IP地址证书的详细申请步骤

IP地址证书申请的条件有两个&#xff0c;一个是此IP必须是公网IP&#xff0c;另一个是IP的80和443端口必须允许短暂开放。满足这两个条件才能为其部署SSL证书。 IP地址ssl证书申请网址链接https://www.joyssl.com/certificate/select/ip_certificate.html?nid16 1 访问提供IP…

【PyTorch实战演练】使用CelebA数据集训练DCGAN(深度卷积生成对抗网络)并生成人脸(附完整代码)

文章目录 0. 前言1. CelebA数据集1.1 核心特性与规模1.2 应用与用途1.3 获取方式1.4 数据预处理 2. DCGAN的模型构建2.1 生成器模型2.2 判别器模型 3. DCGAN的模型训练&#xff08;重点&#xff09;3.1 训练参数3.2 模型参数初始化3.3 训练过程 4. 结果展示4.1 loss值变化过程4…

前端基础知识-ES6解构赋值(将数组内元素、字符串内字符、对象内属性值快速赋值给其他变量)

前言&#xff1a; 将数组、字符串、对象进行展开&#xff0c;并将展开的数据赋值给指定变量&#xff0c;以达到语法简化的目的&#xff0c;日常开发中可以大大提升我们的效率。 主要语法&#xff1a; 一、[变量1,变量2。。。]目标数组 将数组里面的内容赋给其他变量 场景1…

Linux流程控制

if语句 基本格式 if condition thencommand1 fi 写成一行 if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi if-else语句 格式 if condition thencommand1 command2...commandN elsecommand fi if else- if else if condition1 th…

3月空气净化器市场数据分析,热门品牌排行榜揭晓!

三月上旬以来&#xff0c;中国空气净化器行业的规模持续扩大&#xff0c;市场规模和消费需求也在不断提升&#xff0c;消费者对高质量空气的需求增加。智能化是当前空气净化器市场的一个重要发展方向&#xff0c;这类产品集成了空气过滤、监测等功能&#xff0c;满足了现代消费…

信创 | 信创基础设施建设:国内外对比分析研究

信创基础设施建设在国内外的比较分析涉及到多个方面&#xff0c;包括政策支持、产业发展现状、技术进步、市场应用等。通过综合分析&#xff0c;我们可以得出以下结论&#xff1a; 政策支持与发展方向&#xff1a;中国自2019年以来&#xff0c;陆续出台了一系列政策支持信创产业…

[C++基础编程]----预处理指令简介、typedef关键字和#define预处理指令之间的区别

目录 引言 正文 01-预处理指令简介 02-typedef关键字简介 03-#define预处理指令简介 04-#define预处理指令和typedef关键字的区别 &#xff08;1&#xff09;原理不同 &#xff08;2&#xff09;功能不同 &#xf…

贪心+线段树,CF720A. Closing ceremony

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 Problem - 720A - Codeforces 二、解题报告 1、思路分析 如果人都在左上角…

顶级SCI优化!24年新算法冠豪猪算法CPO优化无人机集群三维路径规划!先用先发!

声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ 目录 结果展示 原理讲解 一、路径长度成本 F1 …

斯坦福大学的在线密码学课程

密码学是保护计算机系统信息不可或缺的工具。在本课程中&#xff0c;您将了解密码系统的内部工作原理&#xff0c;以及如何在实际应用中正确使用它们。课程首先将详细讨论当强大的对手窃听和篡改流量时&#xff0c;拥有共享密钥的双方如何进行安全通信。我们将研究许多已部署的…