[IO复用] IO复用问答

news2024/9/24 19:24:42

记录一下IO复用相关的基础知识。

文章目录

    • 阻塞和非阻塞
    • 同步和异步
    • 为什么使用IO复用
    • 什么是IO复用
    • IO复用有哪些方式
      • select IO复用
      • poll IO复用
      • epoll IO复用
      • 什么时候用select 或者 epoll?
    • select、poll、epoll的区别
    • Windows中的IO复用
    • Reactor模式
      • C10K问题
      • C10M问题
    • 面试题
    • 参考

阻塞和非阻塞

如果当前调用的函数,会立即返回,他就是非阻塞的。
入股哦当前函数,不能立即返回,必须等待,就是阻塞的。

我们可以用socket中的recv()函数来举例子,recv通过设置可以设置为阻塞和非阻塞。
当recv()被设置为阻塞时,如果没有收到信息,就会一直等待,直到超时(如果设置了超时),不会继续执行程序。
让recv()被设置为非阻塞时,缓冲区中没有未读取的消息,recv会立刻返回。

关于recv和send的具体情况,可以参考:
[IO复用] recv()和send()的阻塞和非阻塞、返回值、超时

同步和异步

同步和异步是一个相对的概念。
我们需要等待一个函数执行完,再执行下一个函数,这个就是同步。
我们调用一个函数,不用等待它执行完,就继续执行,这个就是异步。
多线程就是实现异步的一种方法。

为什么使用IO复用

想在一个线程中,对多个IO进行管理,就出现了IO复用的方式。

什么是IO复用

IO多路复用,多路指的是可以管理多个网络连接,复用是指的再一个线程中实现。
其功能是就是,通过IO多路复用,实现在一个线程中,监视多个网络socket。
由系统来查询哪些socket有活动,如果有,IO多路复用函数就返回它,用户层进行对应操作。
如果没有活动的socket,就阻塞,让出cpu。

在一个线程中就实现了多个socket的管理,就避免了一个连接一个线程,
数量多了以后导致的资源浪费的情况。

常见的IO模型分为:阻塞IO、非阻塞IO、IO复用、异步IO。
IO复用可以实现reactor模型。
异步IO可以实现proactor模型。

异步IO,Linux中有信号IO、IO uring(Linux 5.1以后)等。
windows有 overlapped IO和IOCP等。

IO复用有哪些方式

IO复用通常有select、poll、epoll,他们都是同步IO。其中epoll是linux中的,windows是没有的。

select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
来源:IO多路复用——深入浅出理解select、poll、epoll的实现

select IO复用

select的函数

int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

select流程

  1. socket()
  2. bind()
  3. listen()
  4. 把listen的fd添加到fd_set数组。
  5. select()。
  6. 对于select()返回的fd_set数组进行遍历,找到可读可写的fd,进行accept、read、send。
    示例代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h> //socket()
#include <unistd.h>  //close()
#include <netinet/in.h> //struct sockaddr
#include <arpa/inet.h> //inet_ntoa()
#include <sys/select.h>

int main()
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	printf("sockfd created.\n");
	
	struct sockaddr_in svraddr = { 0 };
	svraddr.sin_family = AF_INET;
	svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	svraddr.sin_port =  htons(2048);

	if(0 > bind(sockfd, (struct sockaddr*)&svraddr, sizeof(svraddr))){
		return -1;
	}
	printf("bind sockfd.\n");
	
	listen(sockfd,10);	
	printf("listen sockfd.\n");
	int maxfd = sockfd;
	
	fd_set fds,rset; //创建fd_set数组,fds是源数组,rset是用于select函数返回的监控可写状态的数组。
	FD_ZERO(&fds);//清空fds数组
	FD_SET(sockfd,&fds); //把listen的fd添加到fds数组中
	while(1)
	{			
		rset = fds; //把fds赋值给rset,把rset传入select(),让select监控rset中的fd
		int nready = select(maxfd + 1,&rset,NULL,NULL,NULL);
		if(FD_ISSET(sockfd,&rset)) {			//检查是否是listen的fd有事件
			struct sockaddr cliaddr = {0};
			socklen_t len = sizeof(cliaddr);
			int clientfd = accept(sockfd,(struct sockaddr*)&cliaddr,&len);
			maxfd = clientfd;  //fd的最大值需要更新
			FD_SET(clientfd,&fds); //把accept的fd添加到fds中
			
			struct sockaddr_in* ptr = (struct  sockaddr_in*)&cliaddr;
			printf("accept,clientfd=%d,clientaddr=%s,clientport=%d\n",
				clientfd,inet_ntoa(ptr->sin_addr),
				ntohs(ptr->sin_port));
		}
		for(int i = sockfd + 1; i < maxfd + 1; ++i)	{ //检查listen fd以外的,监控的fd是否有事件
			if(FD_ISSET(i,&fds)){
				int clientfd = i;
				char buff[128] = { 0x00 };
				int msg_count = recv(clientfd,buff,sizeof(buff),0);
				if(msg_count == 0){
					printf("clientfd=%d disconnect.\n",clientfd);
					FD_CLR(clientfd,&fds);
					close(clientfd);
					continue;
				}
				printf("recv,clientfd=%d,msg_count=%d,msg=%s\n",clientfd,msg_count,buff);
				send(clientfd,buff,msg_count,0);			
			}
		}
	}
}

select的缺点

  1. fd_set是一个位图,FD_SETSIZE是1024,fd_set最大就能容纳1024位,也就是select能监控的fd是有数量上限1024的。(windows select的默认FD_SETSIZE 是64,可以手动修改为最大1024)。
  2. select的效率不高,因为传入select的fd_set数组,需要从用户态拷贝到内核态,内核态是遍历来检查是否有fd就绪,然后结果传回用户态。而到了用户态,也需要遍历一遍。
  3. 在用户态把fd_set几个数组传入select函数,就有从用户态到内核态的拷贝,高并发的时候,这样开销比较大。

poll IO复用

poll没用过,只是略微了解,这里直接搬运文章中的代码示例。
一网打尽:面试中的 IO 多路复用高频题!

poll函数

#include <poll.h>
// 数据结构
struct pollfd {
    int fd;                         // 需要监视的文件描述符
    short events;                   // 需要内核监视的事件
    short revents;                  // 实际发生的事件
};
 
// API
int poll(struct pollfd fds[], nfds_t nfds, int timeout);

poll示例

// 先宏定义长度
#define MAX_POLLFD_LEN 4096  
 
int main() {
  /*
   * 在这里进行一些初始化的操作,
   * 比如初始化数据和socket等。
   */
 
  int nfds = 0;
  pollfd fds[MAX_POLLFD_LEN];
  memset(fds, 0, sizeof(fds));
  fds[0].fd = listenfd;
  fds[0].events = POLLRDNORM;
  int max  = 0;  // 队列的实际长度,是一个随时更新的,也可以自定义其他的
  int timeout = 0;
 
  int current_size = max;
  while (1) {
    // 阻塞获取
    // 每次需要把fd从用户态拷贝到内核态
    nfds = poll(fds, max+1, timeout);
    if (fds[0].revents & POLLRDNORM) {
        // 这里处理accept事件
        connfd = accept(listenfd);
        //将新的描述符添加到读描述符集合中
    }
    // 每次需要遍历所有fd,判断有无读写事件发生
    for (int i = 1; i < max; ++i) {     
      if (fds[i].revents & POLLRDNORM) { 
         sockfd = fds[i].fd
         if ((n = read(sockfd, buf, MAXLINE)) <= 0) {
            // 这里处理read事件
            if (n == 0) {
                close(sockfd);
                fds[i].fd = -1;
            }
         } else {
             // 这里处理write事件     
         }
         if (--nfds <= 0) {
            break;       
         }   
      }
    }
  }

poll的缺点
poll和select唯一不同就是没有监控fd的上限限制(因为他传入的是用户指定长度的pollfd数组)。
但是由于用户态拷贝到内核态的资源浪费、遍历导致的效率低,还是存在。

epoll IO复用

epoll主要函数

int epoll_create(int size);
int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll示例

#include <stdio.h>
#include <sys/socket.h> //socket()
#include <sys/epoll.h>	//epoll
#include <unistd.h> 	//close()
#include <netinet/in.h> //struct sockaddr

#include <errno.h>		//perror()

int main()
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);

	struct sockaddr_in svraddr;
	svraddr.sin_family = AF_INET;
	svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	svraddr.sin_port = htons(2048);
	if(	bind(sockfd,(struct sockaddr*)&svraddr,sizeof(svraddr)) < 0) {
		perror("bind");
		return -1;
	} 

	listen(sockfd,10);

	int epfd = epoll_create(1); //这是一个链表,想要监听的fd是添加在这里面的 //从Linux 2.6.8开始参数大于0就可以
	struct epoll_event ev;
	ev.events = EPOLLIN;
	ev.data.fd = sockfd; //为要监听的fd设置监听事件
	epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev); //将要监听的socket和事件,添加到epoll_fd中

	struct epoll_event events[1024] = { 0x00 }; //这是用于存储epoll_wait返回的就绪fd的数组
	
	while(1){		
		printf("epoll_wait()\n");
		int nready = epoll_wait(epfd,events,1024,-1); //最后一个参数是timeout,设置成-1就是一直阻塞
		if(nready > 0){
			for(int i = 0; i < 1024; ++i){
				if(events[i].data.fd == sockfd){ //如果就绪的是listen的fd,就accept,并把accept到的fd,添加到epoll_fd中
					struct sockaddr cliaddr;
					socklen_t len = sizeof(cliaddr);
					int clientfd = accept(sockfd,(struct sockaddr*)&cliaddr,&len);
					printf("accept,clientfd=%d\n",clientfd);

					ev.data.fd = clientfd;
					ev.events = EPOLLIN | EPOLLET;
					epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev);
				}	
				else if(events[i].events & EPOLLIN){
					int clientfd = events[i].data.fd;
					char buff[12] = { 0x00 };
					printf("try clientfd=%d recv.\n",clientfd);
					int msg_count = recv(clientfd,buff,sizeof(buff),0);
					if(msg_count == 0){ //recv 返回0,证明连接断开,需要在epoll_fd中删除监听的fd,并且close fd
						printf("clientfd=%d disconnect.\n",clientfd);
						epoll_ctl(epfd,EPOLL_CTL_DEL,clientfd,NULL);
						close(clientfd);
						continue;
					}
					printf("recv,clientfd=%d,msg_count=%d,msg=%s\n",clientfd,msg_count,buff);
					send(clientfd,buff,msg_count,0);
				}
			}
		}
	}	
}

几个注意点
1.struct epoll_event 中有一个成员data,他是一个联合体,可以使用其中的fd直接存储要监听的fd,
也可以通过void *ptr 来存储一个回调的结构体。
void *ptr 的使用可以参考:
[IO复用]epoll_data_t的void *ptr和int fd的使用区别
要注意的是,不同的fd的void *ptr 如果指向了同一个结构体,那么就都会修改这同一个结构体了。
为了避免这种情况,可以为不同的fd 指向不同的结构体对象。

struct epoll_event
{
  uint32_t events;	/* Epoll events */
  epoll_data_t data;	/* User data variable */
} __EPOLL_PACKED;
typedef union epoll_data
{
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;
  1. epoll_create() 的参数,设置一个非零值就可以了。它返回的epoll fd也是个文件描述符,最好使用完close掉。

LT水平触发和边沿触发
epoll 可以设置为水平触发或者边沿触发。
水平触发就是如果可读,读缓冲区中只要有数据,就会一直触发epoll_wait返回fd的EPOLLIN事件。
边沿触发是只在缓冲区有数据可读时候,触发一次epoll_wait返回fd的EPOLLIN事件,即使没有读完,直到下次再有数据流入之前都不会再提示了。

epoll默认就是水平触发,边沿触发需要进行以下设置:

ev.data.fd = acceptfd;
ev.events = EPOLLIN;
ev.events |= EPOLLET; //这里就是对事件设置边沿触发
epoll_ctl(epfd, EPOLL_CTL_ADD, acceptfd, &ev);

伴随边沿触发,往往socket fd也要设置成非阻塞的。

int SetNonBlockFD(int fd) 
{
    int oldflag = fcntl(fd,F_GETFL);
    int newflag = fcntl(fd,F_SETFL, oldflag | O_NONBLOCK);
    if(newflag == -1)
        return -1;
    return oldflag;
}

边缘触发效率更高,减少了事件被重复触发的次数,
这样epoll_wait()函数返回的就绪队列epoll_event *就会更精简。

如果使用边沿模式,必须使用非阻塞 I/O。
在收到一次epoll_wait()通知后,须循环调用 recv等函数,直到返回 EWOULDBLOCK 为止。
然后再调用 epoll_wait 等待操作系统的下一次通知。
如果在边沿模式,使用阻塞IO,虽然也可以用循环重复读取,但读取最后的数据后,IO函数会阻塞,
这就违背了使用边沿模式的初衷。

参考:
[IO复用]EPOLL 如何实现ET(边缘触发)

epoll的优缺点

  1. select用的位图来监控设备,poll用的数组。epoll用的红黑树,没有最大连接数的上限,并且查找和删除更叫高效。
  2. select是用户态在维护fd的集合,所以需在select()的时候把fd集合传给内核。epoll 只需要在EPOLL_CTL_ADD的时候,从用户态传递到内核态,不需要在执行epoll_wait()时的拷贝。
  3. epoll内核态使用的回调函数,来把有事件的fd写到epoll_wait()的返回队列中。返回队列也只有,有事件的fd,不需要像seelct一样把所有fd_set的fd都FD_ISSET遍历一遍了。

什么时候用select 或者 epoll?

在我们上面的内容中,epoll肉眼可见的要比select要好。
但是为什么说到IO复用,还是select、poll、epoll并列,而不是只有epoll呢?
因为每种IO复用方式,都有适用的范围。

用户活跃度高,连接量大不的情况下,select 优于epoll.

当连接数较多并且有很多的不活跃连接时,epoll 的效率比其它两者高很多。当连接数较少并且都十分活跃的情况下,由于 epoll 需要很多回调,因此性能可能低于其它两者。
来源:后端面试必问的I/O多路复用,这一篇就够了!

什么情况下使用Epoll:
1.你的程序通过多个线程来处理大量的网络连接。如果你的程序只是单线程的那么将会失去epoll的很多优点。并且很有可能不会比poll更好。
2.你需要监听的套接字数量非常大(至少1000);如果监听的套接字数量很少则使用epoll不会有任何性能上的优势甚至可能还不如poll。
3.你的网络连接相对来说都是长连接;就像上面提到的epoll处理短连接的性能还不如poll因为epoll需要额外的系统调用来添加描述符到集合中。
4.你的应用程序依赖于Linux上的其他特性
来源:【性能篇】多路复用之 Select,Poll,Epoll 的差异与选择

select、poll、epoll的区别

在这里插入图片描述

来源:IO多路复用——深入浅出理解select、poll、epoll的实现

Windows中的IO复用

Windows中可以使用select 和 poll ,不能使用epoll。

Windows中不但提供了select,还提供了WSAAsyncSelect和WSAEventSelect,但他们是异步IO模型,
后面会记录异步IO。
→[IO复用] Windows select FD_SETSIZE 大小修改

Reactor模式

Reactor 模式也叫 Dispatcher 模式,即IO多路复用监听事件,收到事件后,根据事件类型分配(Dispatch)给某个进程 / 线程。
Reactor模式的思想特点在于,不是应用程序主动调用函数并等待函数执行完成,而是应用程序把回调函数和事件注册在Reactor中,当有事件发生时,自动执行对应的回调函数。

Reactor分为:单Reactor单线程、单Reactor多线程、主从Reactor多线程,3种。
这三种的差异和解释可以看:
Reactor模型

我写过单Reactor多线程模式,我用我的理解,来描述一下:
这个模式主要分为selector、accepter、handler、processor模块。
selector核心是select(),用于监听fd。当监听到listen的fd有事件时,分发给accepter处理。
accepter 中accept到新的fd后,把fd加入fd_set继续监听。
当reactor监听到listen外的fd有事件时,分发给handler模块处理,handler会把相关信息和回调函数,传入线程池处理。

C10K问题

即单个服务器进程如何处理10K个并发连接。
这个问题是早期互联网面临的问题,那时候后并发连接比较少,往往是一连接一线程的处理方式。
解决C10K的方案就是IO复用,尤其是Epoll的出现。之后,linux有Epoll ,windos 有IOCP,可以实现高并发的处理。

C10M问题

C10M问题是C10K问题的升级,到了C10M这个程度,制约程序的是内核进行了太多的切换和调度,要想办法避免线程的切换和调度,就提出了协程的概念。
关于C10K和C10M问题,可以参考:
C10k问题简述

面试题

1、在epoll IO多路复用中,某个socket读到一半,在这个socket上又有读事件来了怎么办?
答:为了避免在同一个socket上再次监听到同一个可读事件,可以在对应的描述符中添加 EPOLL_ONESHOT事件。
其效果是监听到一次事件后就将对应的描述符从监听集合中移除,也就不会再被追踪到了。读操作完成后再把对应的文件描述符重新加入监听集合。
作者:linux
链接:https://www.zhihu.com/question/24200063/answer/2991495235
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2、LT和ET模式下的阻塞与非阻塞?
答:在LT(水平触发)模式下,也是epoll的默认模式,epoll_wait返回可读事件,表明socket一定收到了数据,我们可以使用read函数来读取数据。如果指定读取的数据大于缓冲区数据,无论socket是阻塞还是非阻塞,read函数不会阻塞,会返回实际读取到的数据大小。在read之后再次调用read,如果socket是阻塞的,read将阻塞,直到接收到数据才返回。此时,如果指定读取的数据小于缓冲区中数据,epoll_wait 会继续被触发,因为还有读缓冲区中还有数据没有被读取完。
在ET(边缘触发)模式下,只有新的数据到来时才会触发。如果指定读取的数据小于缓冲区中的数据,epoll_wait 不会被继续触发。因此,使用ET模式时,有数据到来时,必须循环读取读缓冲区中的数据,直到read返回-1,并且errno错误码为EAGAIN,才算读取完了全部缓冲区中的内容。
对于监听的listen_fd,最好使用LT模式,如果使用ET模式会导致高并发情况下,有的客户端会连接不上。如果非要使用ET模式,可以在while循环中调用accept()函数。
对于读写的conn_fd,LT模式下,阻塞和非阻塞效果都一样,因为在阻塞模式下,如果数据读取不完全则返回继续触发,反之读取完则返回继续等待。建议将文件描述符设置为非阻塞。
对于读写的conn_fd,ET模式下,必须使用非阻塞IO,并要求一次性地完整读写完全部数据。因为如果不一次性读取完缓冲区中的全部数据,缓冲区剩余数据不会被 epoll_wait 再次触发。
作者:linux
链接:https://www.zhihu.com/question/24200063/answer/2991495235
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

参考

一网打尽:面试中的 IO 多路复用高频题!

后端面试必问的I/O多路复用,这一篇就够了!

IO多路复用——深入浅出理解select、poll、epoll的实现

【性能篇】多路复用之 Select,Poll,Epoll 的差异与选择

Reactor模型

C10k问题简述

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

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

相关文章

【时间序列篇】基于LSTM的序列分类-Pytorch实现 part3 化为己用

系列文章目录 【时间序列篇】基于LSTM的序列分类-Pytorch实现 part1 案例复现 【时间序列篇】基于LSTM的序列分类-Pytorch实现 part2 自有数据集构建 【时间序列篇】基于LSTM的序列分类-Pytorch实现 part3 化为己用 在一个人体姿态估计的任务中&#xff0c;需要用深度学习模型…

【Vue3】组件通信

Vue3组件通信和Vue2的区别&#xff1a; 移出事件总线&#xff0c;使用mitt代替。vuex换成了pinia。把.sync优化到了v-model里面了。把$listeners所有的东西&#xff0c;合并到$attrs中了。$children被砍掉了。 1. props 若 父传子&#xff1a;属性值是非函数。若 子传父&…

【解决】IntelliJ IDEA 重命名 Shift + F6 失效

IntelliJ IDEA 重命名 Shift F6 失效 问题解决 问题 Idea 重命名 Shift F6 &#xff0c;一直没反应 解决 调查发现原因是微软新版的输入法冲突了。需要设置【使用以前版本的微软拼音输入法】解决兼容性。 设置 -> 时间和语言 -> 区域 -> 语言选项 -> 键盘选项…

ELK之使用Grafana读取ES集群的Nginx日志进行分析展示

一、前提&#xff1a; 直通车 ------------>↓↓↓↓↓↓ 需要ES集群 https://blog.csdn.net/wdy_2099/article/details/125441436需要filebeat https://blog.csdn.net/wdy_2099/article/details/125445893需要logstash https://blog.csdn.net/wdy_2099/article/details/1…

技术变革下职业危机

方向一&#xff1a;技术变革 1.人工智能&#xff08;AI&#xff09;&#xff1a;AI技术的快速发展正在改变各个行业。AI在医疗诊断、金融分析、客户服务以及物流管理等方面都有广泛应用&#xff0c;提高了效率和准确性。但同时也引发了一些道德和道德问题&#xff0c;比如隐私…

Redis的五种常用数据类型详解及相关面试问题

目录 Redis的五种常用数据类型详解 简述 Redis五种基本数据类型 String字符串 常用命令 应用场景 Hash散列表 常用命令 使用场景 List链表 常用命令 应用场景 Set( 集合) 常用命令 应用场景 SortedSet( 有序集合) zset 常用命令介绍 应用场景 面试题常问的数…

【MySQL】打开科技创新的第一生产力

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-EtRkflNU19AGWAkT {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

景联文科技大模型数据集更新!教育题库新增高质量数学题、逻辑推理题及英文题

苏格拉底曾以“点燃火焰”的理念来诠释教育。随着大语言模型在教育中的不断应用&#xff0c;教育与AI的深度融合&#xff0c;让我们看到了“点燃火焰”的理念的更多可能性。 大语言模型可以通过与学生的互动&#xff0c;为他们提供个性化的学习体验&#xff0c;更好地满足学习需…

Maven简述

Maven是用于管理和构建Java项目的工具&#xff0c;提供了一套标准化的项目结构&#xff0c;提供了一套标准化的构建流程&#xff0c;提供了一套依赖管理机制&#xff0c;通过Maven使得所有IDE构建的项目结构完全一样&#xff0c;让项目可以通用。 项目名称下分为src 和 pom.xm…

差分进化算法求解基于移动边缘计算 (MEC) 的无线区块链网络的联合挖矿决策和资源分配(提供MATLAB代码)

一、优化模型介绍 在所研究的区块链网络中&#xff0c;优化的变量为&#xff1a;挖矿决策&#xff08;即 m&#xff09;和资源分配&#xff08;即 p 和 f&#xff09;&#xff0c;目标函数是使所有矿工的总利润最大化。问题可以表述为&#xff1a; max ⁡ m , p , f F miner …

Linux系统SSH远程管理服务

目录 一、SSH服务介绍 1、SSH协议是什么&#xff1f; 2、SSH的优点 3、SSH的客户端与服务端 4、SSH的原理 4.1 公钥首次连接原理 4.2 ssh加密通讯原理 4.2.1 对称加密 4.2.2 非对称加密 4.2 ssh远程登录 二、服务端配置 1、常见配置项 1.1 修改默认端口 1.2 禁止…

Transformer and Pretrain Language Models3-1

content transformer attention mechanism transformer structure​​​​​​​ pretrained language models language modeling pre-trained langue models(PLMs&#xff09; fine-tuning approaches PLMs after BERT applications of masked LM frontiers of PLMs …

【Godot4自学手册】第四节动画状态机-AnimationTree

各位同学大家好&#xff01;今天继续学习Godot4&#xff0c;本节将要学习AnimationTree&#xff0c;来实现控制主人公的动画。 一、AnimationPlay节点介绍 Godot引擎通过AnimationPlay节点实现了最灵活的动画系统&#xff0c;它几乎可以给godot中的任意节点的任意属性添加动画…

携程基于Jira Cloud的敏捷项目管理实践

好的工具可以满足团队在各个成长阶段的管理诉求 实践一&#xff1a;对齐目标/团队OKR/多团队协作战略项目 实践二&#xff1a;以产品为中心的协作框架 实践三&#xff1a;交付团队管理 实践四&#xff1a;和海外子公司对齐&#xff0c;协作

数灵通丨可以实现抖音引流微信小程序了

抖音作为一款火爆的短视频社交平台&#xff0c;吸引了数亿用户的关注和喜爱。除了观看和制作视频外&#xff0c;抖音还提供了跳转到小程序的功能&#xff0c;让用户可以享受更多功能和乐趣。那么&#xff0c;如何在抖音中跳转到小程序呢&#xff1f;以下是详细解答&#xff1a;…

Android 基础技术——View 的宽高

笔者希望做一个系列&#xff0c;整理 Android 基础技术&#xff0c;本章是关于 View 的宽高 Activity Resume 的时候设置或者获取view的宽高是否有效? 回答&#xff1a;不确定。 首次 onResume 无效&#xff0c;二次 onResume 就有效了。 回顾「Android 基础技术——addView 流…

[Python] glob内置模块介绍和使用场景(案例)

Unix glob是一种用于匹配文件路径的模式&#xff0c;它可以帮助我们快速地找到符合特定规则的文件。在本文中&#xff0c;我们将介绍glob的基本概念、使用方法以及一些实际应用案例。 glob介绍 Glob(Global Match)是Unix和类Unix系统中的一种文件名扩展功能&#xff0c;它可以…

eNSP学习——理解交换机Hybird接口的应用

目录 原理概述 实验内容 实验目的 实验步骤 实验拓扑 实验编址 实验步骤 基本配置&#xff08;此处仅以PC1为例&#xff09; 实现组内通信、组间间隔 实现网络管理员对所有网络的访问 原理概述 Hybrid接口既可以连接普通终端的接入链路又可以连接交换机间的干道…

嵌入式面试提问

嵌入式面试问题 1.讲一下STM32的时钟系统 现总结下&#xff1a;首先是时钟源输入时钟信号到单片机&#xff0c;然后单片机对输入的时钟信号进行倍频和分频处理&#xff0c;再将处理后的时钟信号输出至系统&#xff0c;外设或外部接口。   先看这张图&#xff0c;最外面的线上…

[docker] Docker资源管理

一、docker资源控制 Docker通过Cgroup 来控制容器使用的资源配额&#xff0c;包括CPU、内存、磁盘三大方面&#xff0c;基本覆盖了常见的资源配额和使用量控制。Caroup 是ControlGroups的缩写&#xff0c;是Linux 内核提供的一种可以限制、记录、隔离进程组所使用的物理资源(如…