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

news2024/10/3 4:39:35

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

        为每个请求分配一个进程或线程的方式会带来较大的资源开销。创建和切换进程/线程需要消耗系统资源,包括内存、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 4096

int 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);

    
    //epoll
    struct 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.coloradmin.cn/o/1978967.html

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

相关文章

C/C++烟花代码

目录 系列推荐 写在前面 烟花代码 代码分析 运行结果 写在后面 系列推荐 序号目录直达链接1爱心代码https://want595.blog.csdn.net/article/details/1363606842李峋同款跳动的爱心https://want595.blog.csdn.net/article/details/1397222493满屏飘字代码https://want59…

YOLOv8网络轻量化改进之ShuffleNetV2主干

目录 一、理论模型 二、代码修改 一、理论模型 首先是shuffleNet网络的理论介绍部分 论文地址:1807.11164 (arxiv.org) 这里是shufflenetv2网络的主要模块的结构,在网络结构中,通过步长来选择这两种不同的模块。步长为1的时候,对应模块c,步长为2的时候对应模块d。 二、…

Java 文件操作和 IO

1. 认识文件 狭义上的文件&#xff08;file&#xff09;&#xff1a;针对硬盘这种持久化存储的 IO 设备&#xff0c;当我们想要进行数据保存时&#xff0c;往往不是保存成一个整体&#xff0c;而是独立成一个个的单位进行保存&#xff0c;这种独立的单位就被抽象成文件的概念 …

2024第五届华数杯数学建模竞赛C题思路+代码

目录 原题背景背景分析 问题一原题思路Step1:数据读取与处理Step2:计算最高评分&#xff08;Best Score, BS&#xff09;Step3:统计各城市的最高评分&#xff08;BS&#xff09;景点数量 程序读取数据数据预处理 问题二原题思路Step1: 定义评价指标Step2: 收集数据Step3: 标准化…

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

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

RK3568笔记五十二:HC-SR04超声波模块驱动测试

若该文为原创文章&#xff0c;转载请注明原文出处。 一、HC-SR04简介 HC-SR04超声波模块是一种常用于距离测量和障碍物检测的模块。它通过发射超声波信号并接收回波来计算所测量物体与传感器之间的距离。 1、基本原理 TRIG引脚负责发送超声波脉冲串。此引脚应设置为高电平10…

4.指令系统

4.指令系统 指令系统概述—复习指导 一个好的框架是学习掌握知识的捷径&#xff0c;能够帮助我们更快的掌握所学知识&#xff0c;下面从王道计算机组成原理书本第四章-指令系统出发&#xff0c;讲解一下第四章的知识框架组成&#xff0c;下面是指令系统这一章节的思维导图和详…

【doghead】h264测试文件、读取、模拟采集时间戳及packtizer

使用原生代码但是原生仓库没有264文件使用绝对路径但是在wsl2的ubuntu22.04中构建添加264文件路径 clion IDE : 来自RtspServer 的测试264文件 PHZ76 提供的264文件读取264成功:按照帧读取 这里是模拟采集视频帧? 定时器:

【Rust练习】7.引用与借用

练习题来自&#xff1a;https://practice-zh.course.rs/ownership/borrowing.html 1 fn main() {let x 5;// 填写空白处let p __;println!("x 的内存地址是 {:p}", p); // output: 0x16fa3ac84 }其实Rust的借用&#xff0c;就类似C的指针和引用&#xff0c;如果你…

面对挫折和恶语 良好心态非常重要

你等年纪轻轻一遇挫折,便松散懈怠,日后怎成大器? 虽称满腹经纶,却是鸡肠鼠肚,连几句恶语都容它不下,你等要记住,为人者,有大度成大器也!夫处世之道,即应变之术, 岂可偏执一端? 【迷茫时&#xff0c;不妨听听司马仲达的人生格言】https://www.bilibili.com/video/BV1JF411i7…

React--》掌握styled-components重塑React样式管理

想象一下&#xff0c;如果你的React组件不仅能自描述其逻辑&#xff0c;还能直接声明自己的样式&#xff0c;这种“所见即所得”的编程体验是不是让人心动不已&#xff1f;styled-components正是这样一把钥匙&#xff0c;它彻底颠覆了我们对React样式管理的传统认知&#xff0c…

Python 中单例模式实现的几种方式

在设计模式中&#xff0c;单例模式是经常被提及和使用的一种模式。它保证一个类只有一个实例&#xff0c;并提供全局访问点。在Python中&#xff0c;有多种实现单例模式的方法。那么&#xff0c;如何选择合适的方法来实现单例模式呢&#xff1f; 单例模式在Python中的几种实现方…

软件设计师笔记-网络基础知识

计算机网络的发展 计算机网络&#xff08;计算机技术通信技术&#xff09;的发展是一个逐步演进的过程&#xff0c;从简单的具有通信功能的单机系统&#xff0c;到复杂的以局域网及因特网为支撑环境的分布式计算机系统&#xff0c;这一历程经历了多个关键阶段&#xff1a; #me…

大模型是否潜在地进行多跳推理?

人工智能咨询培训老师叶梓 转载标明出处 以往的研究表明&#xff0c;基于Transformer的LLMs能够在参数中存储和检索事实信息&#xff0c;以完成简单提示&#xff0c;例如“Stevie Wonder的母亲是谁”。此外&#xff0c;当必要信息明确给出时&#xff0c;LLMs表现出了显著的上下…

JS优化了4个自定义倒计时

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><title>4个自定义倒计时</title><style>* {margin: 0;padding: 0;box-sizing: border-box;user-select: none;body {background: #0b1b2c;}}hea…

(javaweb)maven--Java项目的构建工具

目录 一.Maven概述 二.Idea导入maven项目 三.maven核心功能--依赖管理 四.依赖管理--依赖传递 五.依赖管理--依赖范围 六.依赖管理--生命周期 一.Maven概述 1.管理和构建Java项目的工具 2.Apache开源&#xff08;源代码开放&#xff09; 3. 4. Maven核心配置文件&#xff…

解题 - 左旋字符串的三种解法(思路)

文章目录 前言&#xff1a; 题干&#xff1a; 解题思路&#xff1a; 思路一&#xff1a; 思路二&#xff1a; 思路三&#xff1a; 小结 前言&#xff1a; 路漫漫其修远兮&#xff0c;吾将上下而求索。 题干&#xff1a; 解题思路&#xff1a; 创建变量k &#xff0c;用…

【知识专栏丨python数分实战】关于电商零售客户细分数据分析及可视化

今天这篇文章将给大家介绍关于电商零售客户细分数据分析及可视化的案例分析。 01 数据整理 导入数据 import pandas as pdimport numpy as npfrom pyecharts.charts import *import pyecharts.options as opts import warningswarnings.filterwarnings(ignore) 数据读取及预…

shell的基础介绍

文章目录 shell数组读取数组关联数组获取数组的长度 Shell运算符算术运算符关系运算符布尔运算符逻辑运算符字符串运算符文件测试运算符 Shell echo命令1.显示普通字符串2.显示转义字符3.显示变量4.显示换行5.显示不换行6.显示结果定向至文件7.原样输出字符串&#xff0c;不进行…