多路I/O复用之select、poll、epoll

一、多进程/多线程模型的不足

        为每个请求分配一个进程或线程的方式会带来较大的资源开销。创建和切换进程/线程需要消耗系统资源,包括内存、CPU 时间等。例如,在一个大规模的服务器环境中,如果同时有数千个请求到来,为每个请求创建单独的进程或线程,可能会迅速耗尽系统的内存资源,导致系统性能下降甚至崩溃。而且,大量的进程/线程切换也会增加 CPU 的负担,降低整体的处理效率。

二、I/O 多路复用技术

         I/O 多路复用技术允许一个进程同时监控多个 Socket 连接。虽然在某一时刻只能处理一个请求,但由于每个请求的处理时间极短,在单位时间内就能处理大量的请求。这种时分复用的方式类似于 CPU 对多个进程的并发处理。以一个在线游戏服务器为例,它可能同时接收来自众多玩家的连接请求,通过 I/O 多路复用,服务器能够高效地处理这些请求,而无需为每个玩家创建单独的进程或线程。

1、select、poll、epoll 内核提供的多路复用系统调用:

        这些系统调用为进程提供了从内核获取多个事件的途径。进程可以通过调用相应的函数,将关注的连接(以文件描述符表示)传递给内核,内核会监控这些连接的状态变化,并将产生了事件的连接返回给进程。这使得进程能够集中处理有数据到达或可进行读写操作的连接,提高了系统的资源利用率和处理效率。

2、select、poll、epoll 获取网络事件的过程

        首先,应用程序将需要监控的所有连接的文件描述符传递给内核。内核会对这些连接进行监控。当有连接发生状态变化,比如有新数据到达、连接关闭等,内核会将这些产生了事件的连接的文件描述符返回给应用程序。应用程序在接收到这些信息后,就可以在用户态中对相应的连接进行处理,例如读取数据、发送响应等。

3、select、poll、epoll 能否实现 C10K:

  • select:由于其存在文件描述符数量限制以及在每次调用时需要在用户态和内核态之间频繁复制大量文件描述符集合的问题,导致其在处理大规模并发连接时效率低下,较难实现 C10K。例如,在一个高并发的聊天应用中,如果使用 select 来处理大量的连接,可能会出现响应延迟、丢包等问题。
  • poll:虽然没有了文件描述符数量的限制,但它在每次调用时仍需要复制大量的文件描述符集合,效率问题依然存在,要实现 C10K 也面临较大挑战。比如在一个大规模的电商网站中,大量用户同时发起请求,如果使用 poll 处理,可能会导致服务器性能下降,影响用户体验。
  • epoll:采用了更高效的事件驱动方式,避免了上述的开销,能够轻松应对 C10K 甚至更高的并发连接需求。以一个热门的在线视频平台为例,使用 epoll 可以有效地处理大量观众同时观看视频时产生的并发连接,保证视频的流畅播放和用户的良好体验。

三、select 实现多路复用的方式


       select 会把已连接的 Socket 放入一个文件描述符集合。这就像是把多个钥匙放在一个盒子里。当调用 select 函数时,会把这个装满钥匙(文件描述符)的盒子拷贝到内核中。内核就像一个检查官,通过逐个遍历盒子里的钥匙来检查是否有网络事件产生。一旦发现某个钥匙对应的 Socket 有事件,就给它做个可读或可写的标记。然后,又把整个盒子拷贝回用户态。回到用户态后,用户程序也得像内核那样,再次遍历这个盒子里的钥匙,找到有标记的、可读或可写的 Socket 进行处理。比如说,想象一个电商网站的服务器,同时处理大量的用户请求。select 就像是一个不太聪明的管理员,每次都要把所有的请求信息(文件描述符集合)搬进搬出内核,还得反复查看每个请求的状态。

1、select 的遍历和拷贝问题

        对于 select 这种方式,需要进行两次对文件描述符集合的遍历。内核态里的遍历就像是在一个大仓库里逐个检查货物是否有问题;用户态里的遍历则像是在一堆已经检查过的货物中再次筛选出有问题的。而且,还会发生两次文件描述符集合的拷贝。这就好比把一箱子东西从一个房间搬到另一个房间,修改后又搬回来,这个过程既繁琐又耗时。以一个在线游戏的服务器为例,大量玩家同时在线,频繁的遍历和拷贝会导致服务器处理请求的速度变慢,玩家可能会感觉到卡顿。

2、select 的文件描述符限制

       select 使用固定长度的 BitsMap 来表示文件描述符集合。这就好比一个固定大小的柜子,只能放一定数量的东西。在 Linux 系统中,它能处理的文件描述符个数由内核中的 FD_SETSIZE 限制,默认最大值为 1024,只能监听 0 到 1023 的文件描述符。假设我们有一个大型的金融交易系统,当并发连接数超过 1024 时,select 就无法有效处理,可能会导致部分交易请求被遗漏或延迟处理。

3、select服务端代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/socket.h>
#include<sys/select.h>
#include<netinet/ip.h>
#include<arpa/inet.h> 
#define BUFSIZE 1024
#define SERROPT 8002
#define SERIP "192.168.117.129"
int main(int argc, char* argv[])                                                                                                                                               
{int lfd,cfd;char buf[BUFSIZE];     socklen_t addrlen;lfd= socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in seraddr, cliaddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(SERROPT);      inet_pton(AF_INET, SERIP ,(void*)&seraddr.sin_addr.s_addr);bind (lfd, (struct sockaddr*)&seraddr, sizeof(seraddr));listen(lfd,128);socklen_t len = sizeof(cliaddr);//已经有了文件描述符监听int nfds;fd_set rset, aset;FD_ZERO(&aset);FD_SET(lfd,&aset);nfds = lfd+1;while(1){rset = aset;int ret = select(nfds,&rset,NULL,NULL,NULL);//上来先阻塞if(ret == -1){perror("select error");exit(1);}printf("select返回\n");if(FD_ISSET(lfd,&rset)) //lfd在rest集合里不会阻塞{int cfd = accept(lfd,(struct sockaddr*)&cliaddr,&len);char dst[256];printf( "client is ok cfd = %d, IP : %s  port : %d successful connection!\n ", cfd,inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,dst,64),ntohs(cliaddr.sin_port));FD_SET(cfd,&aset);if((nfds-1) < cfd){nfds = cfd+1;}}for( int i = lfd + 1;i < nfds ; i++){if(FD_ISSET(i,&rset)){               int rr = read(i, buf,BUFSIZE);if(rr < 0){perror("read errro");exit(1);}else if(rr == 0){FD_CLR(i,&aset);printf("客户端断开连接cfd = %d\n",i);close(i);}else{                  write(STDERR_FILENO,buf,rr);                  write(i,buf,rr);}              }}}         return 0;      
}                                                                                                                                                                                                                                                                                                                                                                    

四、poll 对 select 的改进

        poll 不再使用 BitsMap 来存储关注的文件描述符,而是采用动态数组以链表形式来组织。这就像是从一个固定大小的柜子换成了可以无限延长的输送带,可以容纳更多的文件描述符,突破了 select 的个数限制。但就像前面说的,poll 和 select 在本质上还是很相似的。它们都使用“线性结构”来存储进程关注的 Socket 集合,所以都需要遍历整个集合来找到可读或可写的 Socket,时间复杂度都是 O(n)。并且,同样需要在用户态和内核态之间拷贝文件描述符集合。例如,在一个高并发的社交媒体平台上,如果使用 poll 或 select ,随着并发数的增加,系统性能的损耗会越来越大,可能会出现服务器响应缓慢、消息推送延迟等问题。

1、对poll的理解

       poll 是对 select 的一种改进,主要解决了 select 在文件描述符数量上的限制问题。poll 不再使用 BitsMap 来存储所关注的文件描述符,而是采用动态数组以链表形式来组织。这意味着它能够处理的文件描述符数量不再像 select 那样受到固定大小的限制,而是更多地取决于系统资源和内存空间。在工作原理上,poll 和 select 有一定的相似性。

首先,应用程序需要将关注的文件描述符集合告知内核。与 select 不同的是,poll 使用的动态数组在添加或删除文件描述符时更加灵活。

然后,内核会对这些文件描述符进行监控,检查是否有网络事件产生。同样,这个检查过程也是通过遍历的方式进行的。当有事件发生时,内核会标记相应的文件描述符。

最后,内核将结果返回给用户态的应用程序。应用程序接收到结果后,仍需要通过遍历的方式找到发生事件的文件描述符,并进行相应的处理。

然而,尽管 poll 相较于 select 在文件描述符数量上有所改进,但它仍然存在一些不足之处。由于 poll 仍然采用线性结构来存储文件描述符集合,所以在处理大量文件描述符时,遍历的时间复杂度仍然是 O(n)。这意味着随着并发连接数量的增加,性能损耗会逐渐增大。另外,poll 和 select 一样,都需要在用户态和内核态之间拷贝文件描述符集合,这也会带来一定的性能开销。

总的来说,poll 虽然在一定程度上改进了 select 的不足,但在处理大规模并发连接时,其性能仍然可能无法满足需求。

2、poll服务端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <poll.h>#define SER_PORT 8000
#define MAXFD 1024
#define BUFSIZE 4096int main(int argc, char* argv[])
{int lfd, cfd;struct sockaddr_in ser_addr, cli_addr;socklen_t cli_addrlen = sizeof(cli_addr);ser_addr.sin_family = AF_INET;ser_addr.sin_port = htons(SER_PORT);ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);lfd = socket(AF_INET, SOCK_STREAM, 0);printf("lfd=%d",lfd);int opt = 1;setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(opt));bind(lfd, (struct sockaddr*)&ser_addr,sizeof(ser_addr));listen(lfd, 128);struct pollfd fds[MAXFD];int i;int nfds;nfds = 1;int rr;char buf[BUFSIZE];for(i = 1; i < MAXFD; i++){fds[i].fd = -1;}fds[0].fd = lfd;fds[0].events = POLLIN;int ret;while(1){ret = poll(fds, nfds, -1);if(ret < 0){perror("poll error");exit(1);}else if(fds[0].revents & POLLIN){cfd = accept(lfd,(struct sockaddr*)&cli_addr,&cli_addrlen);char dst[64];printf("client IP : %s  PORT : %d successful connection\n",inet_ntop(AF_INET, &cli_addr.sin_addr.s_addr, dst, 64),ntohs(cli_addr.sin_port));printf("客户端建立连接成功cfd=%d\n",cfd);for(i = 1; i < MAXFD; i++){if(fds[i].fd < 0){fds[i].fd = cfd;fds[i].events = POLLIN;break;}}if(i > nfds-1){nfds = i+1;}}for(i = 1; i < nfds; i++){if(fds[i].fd == -1){continue;}else if(fds[i].revents & POLLIN){rr = read(fds[i].fd, buf, BUFSIZE);if(rr < 0){perror("read error");exit(1);}else if(rr == 0){printf("客户端断开连接\n");close(fds[i].fd);fds[i].fd = -1;}else{write(STDOUT_FILENO, buf, rr);write(fds[i].fd, buf, rr);                    }if(--ret == 0){break;}}}}		return 0;
}

五、epoll实现多路复用的方式

1、epoll 解决 select/poll 问题的第一点

       epoll 在内核中使用红黑树来跟踪所有待检测的文件描述字。红黑树是一种高效的数据结构,其增删改操作的时间复杂度通常为 O(logn)。这与 select/poll 有显著不同,select/poll 内核中没有类似的高效数据结构来保存待检测的 socket 。每次 select/poll 操作时,都需要将整个 socket 集合传入内核,这会导致大量的数据拷贝和内存分配。比如说,在一个大型的网络直播平台服务器中,如果使用 select/poll ,每次都要把成千上万的连接信息完整地传递给内核,这会消耗大量的时间和系统资源。而 epoll 只需将新加入或有变化的单个 socket 传入内核中的红黑树,大大减少了数据传输和内存消耗。

2、epoll 解决 select/poll 问题的第二点

       epoll 采用事件驱动的机制,内核维护了一个链表来记录就绪事件。当某个 socket 有事件发生时,通过回调函数将其加入到就绪事件列表中。当用户调用 epoll_wait() 函数时,只会返回有事件发生的文件描述符的个数,无需像 select/poll 那样轮询扫描整个 socket 集合。以一个在线多人游戏服务器为例,当有大量玩家同时进行操作时,epoll 能够快速准确地获取到有事件发生的连接,而 select/poll 则需要逐个检查所有连接,效率低下。

3、epoll 的优势和能力

        epoll 的方式在监听的 Socket 数量增多时,效率不会大幅降低,能够同时监听的 Socket 数目上限为系统定义的进程打开的最大文件描述符个数。这使得 epoll 成为解决 C10K 问题(处理 10000 个并发连接)的有力工具。比如在一个繁忙的电商网站的服务器端,面对海量的并发请求,epoll 能够轻松应对,保证服务器的高效运行和快速响应。

4、关于 epoll 共享内存的错误观点

        网上有一些错误的观点认为 epoll_wait 返回时,对于就绪的事件,epoll 使用的是共享内存的方式,避免了内存拷贝消耗。但实际上,从 epoll 内核源码中可以看到,在 epoll_wait 实现的内核代码中调用了 __put_user 函数,这表明是将数据从内核拷贝到用户空间,并非使用共享内存。

5、epoll服务端代码

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <sys/epoll.h>
#include <errno.h>
//宏,定义的是服务器的IP地址
#define SERIP "192.168.117.129"
//宏,定义的是服务器的端口号
#define SERPORT 8000
int main(int argc, char* argv[])
{//创建一个TCP流式套接字int lfd = socket(AF_INET, SOCK_STREAM, 0);//给监听套接字绑定地址结构struct sockaddr_in seraddr, cliaddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(SERPORT);inet_pton(AF_INET, SERIP, &seraddr.sin_addr.s_addr);int ret = bind(lfd, (struct sockaddr*)&seraddr, sizeof(seraddr));if(ret < 0){perror("bind error");exit(1);}//设置监听listen(lfd, 64);//epollstruct epoll_event events[1025], event;char buf[5];//epfd表示创建的epoll对象的文件描述符int epfd = epoll_create(256);//将lfd挂在到epoll对象上并注册监听事件event.events = EPOLLIN;//读event.data.fd = lfd;ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &event);//将监听文件描述符挂载到(epoll实例中)红黑树if(ret < 0){perror("48 ctl error");exit(1);}while(1){int eret = epoll_wait(epfd, events, 1025, -1);//触发文件描述符个数if(eret < 0){perror("epoll error");exit(1);}if(eret > 0){int i;for(i = 0; i < eret; i++){if(events[i].events & EPOLLIN){if(events[i].data.fd == lfd){socklen_t addrlen = sizeof(cliaddr);int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &addrlen);printf("客户端建立连接成功cfd = %d\n", cfd);if(cfd<0){perror("accept error");exit(1);}else{event.events = EPOLLIN ;event.data.fd = cfd;ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event);//cfd也挂载到(epoll实例中)if(ret < 0){perror("84");exit(1);}}}else{int rr =read(events[i].data.fd, buf, sizeof(buf));if(rr < 0){perror("read error");exit(1);}else if(rr == 0){printf("客户端断开连接\n");                                epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL);close(events[i].data.fd);                                }else if(rr > 0){write(STDOUT_FILENO, buf, rr);write(events[i].data.fd, buf, rr);}                       }}}}}return 0;
}

6、epoll 的边缘触发(ET)和水平触发(LT)模式

边缘触发模式
        在边缘触发模式下,当被监控的 Socket 描述符上有可读事件发生时,服务器端只会从 epoll_wait 中苏醒一次。这就好比一个只响一次的闹钟,不管你是否响应,它都不再重复提醒。这要求我们的程序必须一次性将内核缓冲区的数据读取完。
例如,在一个实时金融交易系统中,新的交易数据到达时触发边缘模式,如果程序没有一次性读取完所有数据,可能会导致部分交易信息的遗漏,从而影响交易决策。

水平触发模式
         水平触发模式则不同,只要被监控的 Socket 上有可读事件发生,服务器端就会不断地从 epoll_wait 中苏醒,直到内核缓冲区的数据被 read 函数读完。它就像一个坚持不懈的提醒者,直到任务完成为止。
比如在一个文件上传的场景中,如果采用水平触发,服务器会持续通知有数据可读,直到数据全部上传完成。

边缘触发与水平触发的举例
        用快递箱的例子来理解,边缘触发就像是快递箱只通知一次,而水平触发则是不断通知直到快递被取出。这形象地展示了两种模式在通知方式上的本质区别。

两种触发模式的特点
        水平触发意味着只要满足事件条件,比如内核中有数据需要读,就持续传递事件给用户。而边缘触发只有在第一次满足条件时触发,后续不再传递相同事件。
比如在一个在线聊天应用中,水平触发会不断提示有新消息,而边缘触发则在新消息首次到达时通知一次。

边缘触发与非阻塞 I/O 的搭配
       在边缘触发模式中,由于 I/O 事件发生时只通知一次,且不确定能读写的数据量,所以收到通知后应尽可能地读写数据。如果文件描述符是阻塞的,没有数据可读写时,进程会阻塞在读写函数处,导致程序无法继续执行。因此,边缘触发模式一般与非阻塞 I/O 搭配使用,直到系统调用返回特定错误。假设在一个高并发的网络爬虫程序中,使用边缘触发和非阻塞 I/O 可以快速处理大量的网页数据请求,避免因阻塞而影响效率。

触发模式的效率比较
        一般来说,边缘触发的效率高于水平触发,因为它能减少 epoll_wait 的系统调用次数。系统调用伴随着上下文切换的开销,减少调用次数有助于提高性能。在一个大型的数据库服务器中,这种效率的提升对于处理大量并发查询请求至关重要。

select/poll 与 epoll 的触发模式
        select/poll 只有水平触发模式,而 epoll 默认是水平触发,但可以根据应用场景设置为边缘触发模式。这为开发者提供了更多的灵活性来优化程序性能。
以一个视频流服务器为例,如果需要高效处理大量并发连接,可以根据具体需求选择合适的触发模式。

I/O 多路复用与非阻塞 I/O 的搭配
        Linux 手册中关于 select 的说明指出,多路复用 API 返回的事件不一定保证可读写,如果使用阻塞 I/O ,在调用 read/write 时可能导致程序阻塞。因此,为了应对这种特殊情况,最好搭配非阻塞 I/O 。例如,在一个分布式文件系统中,使用多路复用结合非阻塞 I/O 可以更好地处理大量的文件读写请求,提高系统的稳定性和响应性。

六、总结

1、基础的 TCP Socket 编程与阻塞 I/O 模型

        最基础的 TCP 的 Socket 编程采用的是阻塞 I/O 模型。在这种模型下,当进行读写操作时,如果数据未准备好,程序会被阻塞等待,直到数据准备好为止。这意味着在同一时间,一个 Socket 连接只能进行一个操作,基本上只能实现一对一的通信。例如,想象一个简单的在线客服系统,使用阻塞 I/O 模型时,如果一个客服正在处理一个客户的请求并且被阻塞等待数据,那么在此期间无法响应其他客户的请求。

2、多进程/线程模型的局限性

        为了服务更多客户端,传统的方式是使用多进程/线程模型。每来一个客户端连接,就为其分配一个进程或线程来处理后续的读写操作。这种方式在处理少量客户端(如 100 个)时可能还可行,但当客户端数量大幅增加到 10000 个时,就会出现诸多问题。大量的进程/线程调度会消耗大量的 CPU 资源,上下文切换会带来额外的开销,而且每个进程/线程占用的内存也会累积起来,成为系统的沉重负担。比如在一个大型的网络游戏服务器中,如果为每个玩家都分配一个线程,当玩家数量激增时,服务器的性能会急剧下降,可能导致游戏卡顿甚至崩溃。

3、I/O 多路复用的出现

         为了解决多进程/线程模型的问题,I/O 多路复用应运而生。它允许在一个进程里处理多个文件的 I/O 操作,极大地提高了资源利用率和系统的并发处理能力。在 Linux 系统中,提供了 select、poll 和 epoll 这三种 I/O 多路复用的 API 。

4、select 和 poll 的工作原理与缺陷

        select 和 poll 在工作时,需要先将关注的 Socket 集合从用户态拷贝到内核态。内核检测事件时,需要遍历这个集合来找到有事件的 Socket 并设置其状态,然后再把整个集合拷贝回用户态。用户态程序同样需要遍历集合来找到可读/可写的 Socket 进行处理。当客户端数量众多,即 Socket 集合很大时,这种频繁的遍历和拷贝操作会带来极大的开销。假设我们有一个大型的电商网站服务器,在高峰期可能有成千上万的客户端连接,如果使用 select 或 poll ,这些遍历和拷贝操作会严重影响服务器的响应速度和处理能力。

5、epoll 解决问题的方式

         epoll 在内核中使用红黑树来管理待检测的 Socket ,红黑树的高效性使得增删改操作的时间复杂度为 O(logn) ,避免了像 select/poll 那样每次操作都传入整个 Socket 集合,从而减少了数据拷贝和内存分配。同时,epoll 采用事件驱动机制,内核通过维护一个链表来记录就绪事件,只将有事件的 Socket 集合传递给应用程序,无需像 select/poll 那样轮询整个集合,大大提高了检测效率。例如,在一个高并发的金融交易系统中,epoll 能够快速准确地处理大量的交易请求,保证系统的稳定和高效运行。

6、epoll 的触发方式优势

        epoll 支持边缘触发和水平触发两种方式,而 select/poll 只支持水平触发。一般来说,边缘触发的方式效率更高,因为它能减少不必要的系统调用和事件检测。比如在一个实时视频流服务器中,边缘触发可以更及时地处理数据,提供更流畅的视频播放体验。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

01 LVS负载均衡群集

集群 在互联网应用中&#xff0c;随着站点对硬件的性能、响应速度、服务稳定性、数据可靠性等要求越来越高&#xff0c;单台服务器越来越力不从心 集群的含义 Cluster&#xff0c;集群也叫群集由多台主机构成&#xff0c;但对外只表现为一个整体 集群分类 类型 负载均衡集…

C++初学(10)

10.1、共用体 共用体是一种数据格式&#xff0c;它能够存储不同的数据类型&#xff0c;但只能同时存储其中的一种类型。比如说&#xff1a;结构可以同时存储int、long、和double&#xff0c;而共用体只能存储int、long、或double。共用体的句式与结构相似&#xff0c;但含义不…

《Milvus Cloud向量数据库指南》——Zilliz Cloud 高可用性深度解析:赋能GenAI应用,引领非结构化数据新纪元

在人工智能与大数据技术日新月异的今天,非结构化数据的处理与分析已成为推动行业智能化转型的关键驱动力。Zilliz Cloud,作为基于开源向量数据库Milvus构建的全托管解决方案,不仅革新了非结构化数据的存储与查询方式,更以其卓越的高可用性设计,为开发人员构建高效、可靠的…

c++----内存管理

okk&#xff0c;大家好。我们大家学习了鄙人的前面前面几篇博客&#xff0c;并且还稍微使用了一些c的基础知识。并且我们前面都说过&#xff0c;我们前面学习的知识都说过。我们前面的几篇博客都是我们以后使用c基础。但是我们大家都知道现在代码都关注什么时间啊&#xff0c;内…

【linux深入剖析】初识线程---线程概念

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 1. Linux线程概念什么是线…

k8s中yaml文件的编写

目录 1.编写pod.yaml 2.编写deploment.yaml 3.编写service.yaml关联创建的pod 4.总结获取K8S资源配置清单文件模板方法 方法1&#xff1a;根据现有资源导出yaml文件修改配置&#xff0c;重新创建 方法2&#xff1a;根据现有资源&#xff0c;进入其配置中&#xff0c;复制…

中国AI大模型场景探索及产业应用调研报告

AI大模型发展态势 定义 AI大模型是指在机器学习和深度学习领域中&#xff0c;采用大规模参数(至少在一亿个以上)的神经网络模型&#xff0c;AI大模型在训练过程中需要使用大量的算力和高质量的数据资源。 产业规模 2023年&#xff0c;中国大模型市场规模为147亿。结合《202…

滚雪球学Java(65-5):面对Properties的各种坑,你需要知道的Java小技巧

咦咦咦&#xff0c;各位小可爱&#xff0c;我是你们的好伙伴——bug菌&#xff0c;今天又来给大家普及Java SE相关知识点了&#xff0c;别躲起来啊&#xff0c;听我讲干货还不快点赞&#xff0c;赞多了我就有动力讲得更嗨啦&#xff01;所以呀&#xff0c;养成先点赞后阅读的好…

C++初学者指南-5.标准库(第二部分)--移除元素算法

C初学者指南-5.标准库(第二部分)–移除元素算法 文章目录 C初学者指南-5.标准库(第二部分)--移除元素算法remove / remove_ifremove_copy / remove_copy_ifunique / unique_copyerase / erase_if相关内容 不熟悉 C 的标准库算法&#xff1f; ⇒ 简介 remove / remove…

【解决error】安装torch 1.1.0

第一步 当云服务器上没有指定版本的torch可以选择 先确定指定torch符合的python版本号 例如这里&#xff0c;我需要安装torch1.1.0&#xff0c;所以需要python>3.6 且 python<3.7 根据python版本&#xff0c;确定服务器安装torch的版本配置 第二步 新建conda虚拟环境…

iPhone苹果密码解锁工具专业版_不限制电脑

iPhone苹果密码解锁工具专业版_不限制电脑 Aiseesoft iPhone Unlocker&#xff1a;轻松解锁iPhone。功能强大&#xff1a;一键移除4位、6位密码、Touch ID和Face ID。 隐私保护&#xff1a;创建密码&#xff0c;安全无忧。数据提醒&#xff1a;解锁时&#xff0c;注意数据和设…

docker部署可执行的jar

1.将项目打包&#xff0c;上传到服务器的指定目录 2.在该目录下创建Dockerfile文件 3.Dockerfile写入如下指令 # 基于哪个镜像 FROM java:8 # 拷贝文件到容器&#xff0c;也可以直接写成ADD xxxxx.jar /app.jar ADD springboot-file-0.0.1.jar file.jar RUN bash -c touch /…

用TypeScript完成的贪吃蛇小游戏

食物类Fod // 定义 class Food {// 定义一个属性表示食物所对应的元素element:HTMLElement;constructor(){//加个&#xff01;表示不能为空,非空断言操作符 //获取页面中的food元素并将其赋值给element this.elementdocument.getElementById(food)!;}// 定义一个获取食物x轴坐…

(六)activiti-modeler 设计器属性编辑弹窗bug修复

BUG重现 在使用流程设计器时&#xff0c;经常碰到弹窗不小心关闭&#xff0c;比如不小心点击了灰色背景上&#xff0c;此时BUG就出现了。弹窗被关闭了&#xff0c;分配用户属性被置空了&#xff0c;以前有数据也被清空了&#xff0c;还无法再次点击弹窗编辑。 不仅仅是分配用…

【Vue3】组件通信之v-model

【Vue3】组件通信之v-model 背景简介开发环境开发步骤及源码总结 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的…

LDR6020快充线,科技与便捷的新宠

在快节奏的现代生活中&#xff0c;充电效率成为了我们不可忽视的一个重要因素。随着智能手机、平板电脑等电子设备的普及&#xff0c;快充线以其独特的优势逐渐成为充电设备市场的新宠。 快充线&#xff0c;相比于传统的普通充电线&#xff0c;快充线在充电速度上有着显著的提升…

五、一个quad同时支持pcie和sfp两种高速接口的ref时钟配置

项目描述 上位机将截图数据通过 XDMA 写入到 FPGA 侧的 DDR 内存区域 1 中通过 axi_lite 接口给 axi_read_start 信号&#xff0c;通知 AXI_read 模块启动读取数据&#xff0c;然后通过 GTP TX 模块发送出去。经过光纤回环&#xff0c;GTP RX 端接收到数据&#xff0c;送给 AX…

微分方程的数值解法——Runge-Kutta (RK4)

Runge-Kutta (RK4)   The Runge-Kutta (RK4) methods are used to solve the solution of the non-liner ordinary differential equation. Here, we will simply summary this method.   Assume the Intial Value Piont (IVP) is satisfied: y ′ f ( t , y ) , y ( t 0 )…

python-查找元素3(赛氪OJ)

[题目描述] 有n个不同的数&#xff0c;从小到大排成一列。现在告诉你其中的一个数x&#xff0c;x不一定是原先数列中的数。你需要输出最后一个<x的数在此数组中的下标。输入&#xff1a; 输入共两行第一行为两个整数n、x。第二行为n个整数&#xff0c;代表a[i]。输出&#x…

椭圆曲线加法运算

1. 定义 椭圆曲线 (Elliptic Curve) 不是函数&#xff0c;而是一条平面曲线&#xff0c;其方程是定义如下&#xff1a; y 2 x 3 a x b y^2x^3axb y2x3axb 其中&#xff0c;判别式 Δ − 16 ( 4 a 3 27 b 2 ) ≠ 0 \Delta -16(4a^327b^2)\neq 0 Δ−16(4a327b2)0。判别…