8.21 腾讯云一面

1.爬虫项目如何处理重复资源?

            yield Request('https://www.zhihu.com',
                          meta={'cookiejar':response.meta['cookiejar']},
                          headers=self.headers_zhihu,
                          callback=self.parse_index,
                          dont_filter=True
                          )

scrapy默认过滤掉重复的之前爬过的url,在request参数中添加dont_filter=True。

scrapy的url去重原理:
1.需要将dont_filter设置为False开启去重,默认是True,没有开启去重;

2.对于每一个url的请求,调度器都会根据请求得相关信息加密得到一个指纹信息,并且将指纹信息和set()集合中的指纹信息进行比对,如果set()集合中已经存在这个数据,就不在将这个Request放入队列中。如果set()集合中没有存在这个加密后的数据,就将这个Request对象放入队列中,等待被调度。

scrapy框架:
Scrapy框架主要由六大组件组成,它们分别是
调试器(Scheduler)、
下载器(Downloader)、
爬虫(Spider)、
中间件(Middleware)、
实体管道(Item Pipeline)、
Scrapy引擎(Scrapy Engine)

工作流程:
在这里插入图片描述

Scrapy的工作原理


2.code题:使用两种不同方法完成中序遍历?

涉及节点结构体:

struct BTreeNode{
    //二叉树
    int data;
    BTreeNode *lchild;
    BTreeNode *rchild;
};

struct snode{
    //设置访问标识
    BTreeNode *node;
    bool flag;
};

方法一:递归方法

void InOrder(BTreeNode *t){
    if(t == NULL)
    	return ;
    	
    InOrder(t->lchild);
    cout<<t->data<<' ';
    InOrder(t->rchild);
}

方法二:不断访问其左子树,然后输出其根,然后访问其接着的右子树,重复过程

void InOrder(BTreeNode *t){
	stack<BTreeNode*> s;
    while(!s.empty() || t != NULL){
        while(t != NULL){
            s.push(t);
            t = t->lchild;
        }
        
        if(!s.empty()){
            t = s.top();
            s.pop();
            cout<<t->data<<' ';
            t = t->rchild;
        }
    }
}

方法三:按右根左的存放方式存入栈,根据栈的特性输出中序遍历。注意第一次放入的是根,第二次放入才调整其位置


void InOrder(BTreeNode *t){
    stack<snode> s;
    snode temp,ltemp,rtemp;
    temp.flag = false;
    temp.node = t;
    s.push(temp);
    
    while(!s.empty()){
        temp = s.top();
        s.pop();
        if(temp.flag)
        	cout<<temp.node->data<<' ';
        else{
            if(temp.node->rchild != NULL){
                rtemp.flag = false;
                rtemp.node = temp.node->rchild;
                s.push(rtemp);
            }
            temp.flag = true;
            s.push(temp);
            
            if(temp.node->lchild != NULL){
                ltemp.flag = false;
                ltemp.node = temp.node->lchild;
                s.push(ltemp);
            }
        }
    }
}

3.TCP为什么是三次握手?

在这里插入图片描述
1、为什么不是2次握手呢?

防止客户端失效的连接请求报文段突然又传到服务器

例如以下情况如果使用两次握手:

如果客户端向服务器发送第一次连接请求在网络节点上滞留了,没有收到服务器的确认,于是又重新发送了一次连接请求
服务器收到客户端的第二次请求发送确认,则连接建立完成
服务器客户端进行数据传输,传输完成断开连接。
此时,在网络上滞留的客户端第一次连接请求到达服务器,服务器发送确认连接但是客户端实际上并没有发送请求,因此不会理睬服务器发送的请求。但是服务器认为连接已完成,并等待客户端进行数据传输。这样会造成资源的浪费
如果采用三次握手的话:

滞留在网络上的客户端第一次请求到达服务器之后,服务器发送确认,但实际上服务器并没有发送请求,因此不会理睬服务器的确认,故不会发送确认,服务器等不到客户端的确认则连接建立失败。这样就防止了客户端失效的连接请求报文段突然又传到服务器。

2、为什么不是4次握手?

我们来模拟一下4次握手的过程

(1) 客户端发送请求报文,发送自己的序列号
(2) 服务器发送确认报文
(3) 服务器发送自己的序列号
(4) 客户端发送确认报文

上述过程中2、3步完全可以合为一步进行发送,因此完全没有必要进行4次握手


4.有什么措施降低TCP连接(三次握手)的性能损耗?

在这里插入图片描述

1.HTTP1.1长连接技术

长连接,HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。

2.HTTP2多路复用技术

多路复用(MultiPlexing),即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。

https://www.cnblogs.com/heluan/p/8620312.html


5.什么是HTTP的多路复用?

在这里插入图片描述

在 HTTP 1.1 中,发起一个请求是这样的:

浏览器请求 url -> 解析域名 -> 建立 HTTP 连接 -> 服务器处理文件 -> 返回数据 -> 浏览器解析、渲染文件

这个流程最大的问题是,每次请求都需要建立一次 HTTP 连接,也就是我们常说的3次握手4次挥手,这个过程在一次请求过程中占用了相当长的时间,而且逻辑上是非必需的,因为不间断的请求数据,第一次建立连接是正常的,以后就占用这个通道,下载其他文件,这样效率多高啊!

为了解决这个问题, HTTP 1.1 中提供了 Keep-Alive,允许我们建立一次 HTTP 连接,来返回多次请求数据。

但是这里有两个问题:

HTTP 1.1 基于串行文件传输数据,因此这些请求必须是有序的,所以实际上我们只是节省了建立连接的时间,而获取数据的时间并没有减少

最大并发数问题,假设我们在 Apache 中设置了最大并发数 300,而因为浏览器本身的限制,最大请求数为 6,那么服务器能承载的最高并发数是 50

而 HTTP/2 引入二进制数据帧和流的概念,其中帧对数据进行顺序标识,这样浏览器收到数据之后,就可以按照序列对数据进行合并,而不会出现合并后数据错乱的情况。同样是因为有了序列,服务器就可以并行的传输数据。

HTTP/2 对同一域名下所有请求都是基于流,也就是说同一域名不管访问多少文件,也只建立一路连接。同样Apache的最大连接数为300,因为有了这个新特性,最大的并发就可以提升到300,比原来提升了6倍。

https://www.jianshu.com/p/ff8f0bd78942


6.I/O多路复用各模型区别?

在这里插入图片描述
多路复用的本质是同步非阻塞I/O,多路复用的优势并不是单个连接处理的更快,而是在于能处理更多的连接。

I/O多路复用技术通过把多个I/O的阻塞复用到同一个select阻塞上,一个进程监视多个描述符,一旦某个描述符就位, 能够通知程序进行读写操作。因为多路复用本质上是同步I/O,都需要应用程序在读写事件就绪后自己负责读写。

最大的优势是系统开销小,不需要创建和维护额外线程或进程。

目前支持多路复用的系统调用有select, poll, epoll。

select & poll & epoll比较:

  1. 每次调用select都需要把所有要监听的文件描述符拷贝到内核空间一次,fd很大时开销会很大。epoll会在epoll_ctl()中注册,只需要将所有的fd拷贝到内核事件表一次,不用再每次epoll_wait()时重复拷贝。

  2. 每次select需要在内核中遍历所有监听的fd,直到设备就绪;epoll通过epoll_ctl注册回调函数,也需要不断调用epoll_wait轮询就绪链表,当fd或者事件就绪时,会调用回调函数,将就绪结果加入到就绪链表。

  3. select能监听的文件描述符数量有限,默认是1024;epoll能支持的fd数量是最大可以打开文件的数目,具体数目可以在/proc/sys/fs/file-max查看。

  4. select, poll在函数返回后需要查看所有监听的fd,看哪些就绪,而epoll只返回就绪的描述符,所以应用程序只需要就绪fd的命中率是百分百。

文件描述符:
文件描述符用于表示指向文件引用的抽象画概念。在形式上是一个非负整数,实际上是一个索引值,指向内核为每一个进程维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回个文件描述符。

表面上看epoll的性能最好,但是在连接数少并且链接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。

select, poll, epoll具体工作过程见:https://www.jianshu.com/p/439e8b349f48


7.Epoll模型的边缘触发模式?一共10KB数据,通知了进程I/O,如果进程读到8k就马上去做其他事了,剩下2KB数据还能读到吗?

在这里插入图片描述
Level_triggered(水平触发): 当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小), 那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!!

Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!

总结

1.对于监听的sockfd,最好使用水平触发模式,边缘触发模式会导致高并发情况下,有的客户端会连接不上。如果非要使用边缘触发,网上有的方案是用while来循环accept()。

2.对于读写的connfd,水平触发模式下,阻塞和非阻塞效果都一样,不过为了防止特殊情况,还是建议设置非阻塞。

3.对于读写的connfd,边缘触发模式下,必须使用非阻塞IO,并要一次性全部读写完数据。

https://blog.csdn.net/liqihang_dev/article/details/104508677


8.协程的通信方式?

不同协程之间如何通信;

(1):不同协程之间可能同时对一块内存进行操作,导致数据的混乱,即并发/并行不安全;主协程运行完了,计算阶乘的协程却没有运行完,功能并不能够准确实现;可利用互斥锁解决该问题;
(2):可以利用利用管道。

为什么需要管道?

(1)主线程在等待所有协程全部完成的时间很难确定;

(2)如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能协程还处于工作状态,这时也会随着主协程的结束而销毁;

(3)通过全局变量加锁同步来实现通讯,也并不利于多个协程对全局变量的读写操作;

管道的介绍:

(1)管道的本质就是一种数据结构–队列;

(2)数据先进先出;

(3)线程安全,多协程访问时,不需要加锁;

(4)管道只能存储相同的数据类型;

注意:管道容量满了则不能继续写入,在没有使用协程的情况下,管道空了不能继续读取。

https://www.cnblogs.com/xiximayou/p/11952988.html#top


9.阻塞channel和非阻塞channel?

一句话总结:ch := make(chan int) 由于没有缓冲发送和接收需要同步,ch := make(chan int, 2) 有缓冲不要求发送和接收操作同步。

1、无缓冲时,发送阻塞直到数据被接收,接收阻塞直到读到数据。

2、有缓冲时,当缓冲满时发送阻塞,当缓冲空时接收阻塞。


10.线程如何保护共享资源?线程可以有独占的变量吗?

c++ 线程间通信方式:

  1. 互斥锁

  2. 读写锁

  3. 信号量

  4. 条件变量

线程共享的内容包括:

  1. 进程 代码段
  2. 进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)
  3. 进程打开的文件描述符
  4. 信号的处理器
  5. 进程的当前目录
  6. 进程用户 ID 与进程组 ID

线程独有的内容包括:

  1. 线程 ID
  2. 寄存器组的值
  3. 线程的栈
  4. 错误返回码
  5. 线程的信号屏蔽码

https://blog.csdn.net/gaojing303504/article/details/81450999


11.进程的通信方法?

  1. 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
  2. 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  3. 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  4. 共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
  5. 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  6. 套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
  7. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

https://blog.csdn.net/zhaohong_bo/article/details/89552188


12.free底层在干什么,具体如何进入到内核态把内存回收的?

截取一段简单代码说明:

int min_z=1;
for(int z=min_z; z<=max_z; z++)
{
	int *vert_x=(int *)malloc(sizeof(int) *NN_z[z-min_z]);
}
free(vert_x)

1、free只是释放了malloc所申请的内存,并不改变指针的值;free的是vert_x所指向地址的内存。free只是释放了指针所指向地址的空间,本质上就是做了一些标记而已,所以指针及空间内容都还是存在的,只不过有隐患罢了。

2、free(vert_x)释放了vert_x指示的内存空间,vert_x这个指针变量本身仍然存在。这就是悬垂指针问题

3、由于指针所指向的内存已经被释放,所以其它代码有机会改写其中的内容,相当于该指针从此指向了自己无法控制的地方,也称为野指针

4、为了避免失误,最好在free之后,将指针指向NULL。

比喻的形象说法:

内核通过一个红黑树来记录了空闲的内存,malloc就是从树中查找一块大小适合的内存并把地址给你,然后把这个节点从树中摘除,避免被别人分配到产生冲突。这个内存现在归你一个人用了。

free函数是把你的这个内存重新放回到红黑树中,让别人可以申请到这个内存。从逻辑上来说,你现在不能在使用这个内存了,因为它已经不属于你。但是系统的实现上目前没有做到,所以你还是能访问这个地址。

另外,系统也不会帮你覆盖内存中的数据,因为做这一个操作浪费时间,没有必要。

办法:

  1. 将对应的内存块标记为可用(即可被本进程或其它进程申请占用)。
  2. 在free之后,将指针指向NULL

https://blog.csdn.net/u014546553/article/details/53609038

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值