招银面试项目面经

1.前后端即时通讯方式

①Ajax:异步JavaScript和XML

原理:Ajax利用JavaScript和XMLHttpRequest对象实现在后台与服务器进行异步数据交换,通过更新部分页面而不刷新整个页面。

优势:异步更新、部分更新、跨域通信,适用于需要动态更新页面内容、实现数据局部刷新、与服务器进行异步通信的场景。

②WebSocket:全双工通信协议

原理:WebSocket是一种全双工的通信协议,通过在客户端和服务器之间建立持久连接,实现实时性的双向通信。

优势:实时通信、低延迟、较少的数据传输,适用于实时性要求高、服务器主动推送数据,聊天应用、实时协作工具等。

③RESTful API:面向资源的Web服务架构

原理:RESTful API基于HTTP协议,通过资源进行表示和操作,实现客户端和服务器之间的数据交互。

优势:简洁易用、跨平台兼容、缓存支持(基于HTTP协议的缓存机制,提高高效的缓存支持)适用于构建可扩展的Web服务和API,实现资源的增删改查操作,如应用程序接口、移动应用后端等

2.多线程为什么能提高执行效率:

①在多核处理器上,将要执行的任务分成多个可并行执行的线程,就可以提高执行效率。

②在单核处理器上,多线程只能并发执行,而不是并行,并发原理,其实就是cpu快速来回切换,在特定的时间执行特定的某一个任务,并发执行存在这线程间上下文切换的问题,会消耗一定的时间。如果不考虑阻塞,多线程并发执行其实比单线程执行更加耗费时间,线程过多也会造成cpu负荷过大,并且线程占用内存资源,创建销毁线程也都是需要开销的。

  多线程通过提高cpu利用率来提高效率。数据库访问、磁盘io等操作的速度比cpu执行代码的速度慢很多,单线程环境下,这些操作会阻塞程序的执行,导致cpu空转,等待着上述操作完成,因此对于会产生这些阻塞的程序来说,使用多线程可以避免在等待期间cpu的空转,提高cpu的利用率。

3.多线程的好处

主要原因是许多应用程序中同时发生多个活动,某些活动随着时间的推移而阻塞,将这些应用程序分解成并发运行的多个线程,简化设计模型。同时多线程有共享同一地址空间和可用数据的能力,这是多进程没有的。

线程比进程开销小,更容易创建和释放。多个线程是IO密集型时,多线程可以使这些活动彼此重叠运行,可以加快程序执行的速度。

4.进程空间、堆、栈内存

Linux的虚拟地址空间范围为0-4G(32位操作系统)

每个进程运行在属于自己的虚拟地址空间中,在内存中,由页表映射到物理内存。虚拟地址保护内核空间不被用户破坏。

Linux内核将4G空间分为两部分:

(0xC0000000-0XFFFFFFFF)供内核使用,称为内核空间;(0xC0000000-0xBFFFFFFF)3G的字节供各个进程使用,称为“用户控件”。

可以通过系统调用进入内核,内核由所有进程共享。

栈内存:遵循FIFO的顺序,由进程控制,操作系统提供了大小限制,如果申请过多会有栈溢出错误。

堆内存:提供对应的API(malloc/calloc/realloc/free),由用户控制 ,需要程序员自己释放,或者程序结束时,由OS回收,由链表进行控制,查找空闲链表进行分配内存(会含有内存碎片)

5.内存映射机制(MMU)

为什么C++中delete一块内存后,该内存不可复用,因为有内存映射机制(由MMU提供),访问的不是真实地址而是一种为每个进程分配的虚拟地址空间,这个地址需要映射到真实内存地址才是访问的实际地址,当释放内存后,这个地址不在映射到有效的内存空间,会访问失败或者异常。

6.内存池的实现:

  • 最简单的内存分配器:做一个链表指向空闲内存,分配就是取出来改写链表,返回;释放就是放回到链表里,并做好归并。优点:实现简单;缺点:分配时搜索合适的内存块效率低,释放回归内存后归并消耗大。
  • 定长内存分配器:实现一个FreeList,每个FreeList用于分配固定大小的内存块,每个里面有两个链表,OpenList用于存储未分配的空闲对象,CloseList用于存储已分配的内存对象,所谓的分配就是从OpenList取出一个对象放到CloseList里并返回给用户,释放又是从CloseList移回到OpenList。优点:简单粗暴解决特定场景下的问题有效;缺点:功能单一,只能解决定长的内存需求,另外占着内存没有释放。
  • 哈希映射的FreeList池:在定长分配器的基础上,按照不同对象大小,构造固定的内存分配器分配时按照申请内存的大小进行对齐然后查找H表,决定由哪个分配器负责,分配时在内存头部写上cookie,便于释放时正确归还。缺点:在高并发环境下,锁竞争激烈,效率降低。
  • 并发内存池:分为三层Thread Cache(线程缓存时每个线程所独有的)、Center Cache(所有线程共享,需要加锁)、Page Cache(存储的是以页为单位存储及分配的、回收Center Cache满足条件引用计数为0对象并合并相邻的项)

怎么实现每个线程都拥有自己唯一的线程缓存呢:为了避免加锁带来的效率,在Thread Cache中使用TLS(现成本地存储)保存每个线程本地的Thread Cache指针,这样Thread Cache在申请释放内存是不需要加锁的。因为每个线程都拥有了自己唯一的一个全局变量。

7.进程切换为何比线程慢

因为进程切换涉及虚拟地址空间的切换而线程不会。每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,所以同一个进程中的线程进行线程切换时不涉及虚拟地址空间的切换。把虚拟地址转换为物理地址需要查找页表,页表查找是一个很慢的过程(至少访问两次内存),因此通常使用Cache来缓存常用地址映射,但进程切换后页表也要进行切换,则TLB(快表)会失效,导致转换变慢。

8.操作系统:几个信号产生函数

①kill函数

#include<sys/types.h>
#include<signal.h>

/*
    @param pid > 0:将信号传送给ID为pid的进程
           pid = 0:将信号传送给当前进程所在进程组中的所有进程
           pid = -1:将信号传送给系统内所有进程
           pid < -1:将信号传送给|pid|所在进程组的所有进程
*/
int kill(pid_t pid, int sig);

②int raise(int sig)   等价于kill(getpid(), sig());

③void abort(void)  等价于 kill(getpid(), SIGABRT);

④alarm函数(闹钟)

#include<unistd.h>

//设置定时器(闹钟),在指定的seconds后内核会给当前进程发送14)SIGALRM信号
//进程收到该信号,默认动作终止,每个进程有且仅有唯一的一个定时器
unsigned int alarm(unsigned int seconds);

⑤setitimer函数(定时器)

#include<sys/time.h>
struct itimerval{
    struct timerval it_interval;//闹钟触发周期
    struct timerval it_value;   //闹钟触发时间
};
struct timeval{
    long tv_sec;
    long tv_usec;
};
/*
    设置定时器(闹钟),可代替alarm函数。精度微秒,可以周期定时
    @param which 指定定时方式①自然定时   14)SIGALRM
                            ②虚拟空间定时  26)SIGVTALRM只计算占用cpu的时间
    @param new_value 负责设定timeout时间
    @param old_value 存放旧的timeout值,一般为NULL
*/
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

9.如何避免僵尸进程

  • 最简单的办法:父进程通过wait()waitpid()等函数等待子进程结束,但这样会导致父进程挂起
  • 如果父进程要处理的事很多,不能够挂起,通过signal()函数认为处理信号SIGCHLD:只要有子进程退出自动调用指定好的回到函数,因为子进程结束后父进程会收到该信号SIGCHLD,可以在其回调函数里调用wait或waitpid()回收
  • 如果父进程不关心子进程什么时候结束,可以用signal(SIGCHLD,SIG_IGN)通知内核回收。

10.守护进程

是linux中的后台服务进程,他是一个生存周期较长的进程,通常独立于控制终端并且周期性的执行某些任务,或等待处理某些发生的事件;一般采用以d结尾的名字;所有的服务存在于etc/init.d;守护进程是个特殊的孤儿进程;之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。(步骤:创建子进程,父进程必须退出;在子进程上创建新会话;改变当前目录为根目录;执行核心工作)

11.线程池

线程池:创建一批线程后不再进行释放,有任务就提交给这些线程处理,因此无需频繁的创建、销毁线程,同时由于线程池中的线程个数通常是固定的,也不会消耗过多的内存,复用、可控。

采用数据结构中的队列适合该场景,提交任务的就是生产者,消费任务的线程就是消费者,经典的生产者-消费者模式。(当缓冲区满的时候,生产者会进入休眠状态,当下次消费者开始消耗缓冲区的数据时,生产者才会被唤醒,开始往缓冲区中添加数据;当缓冲区空的时候,消费者也会进入休眠状态,直到生产者往缓冲区中添加数据时才会被唤醒)

提交给线程池的任务包含两部分:①需要被处理的数据,②处理函数的方法

线程池中的线程会阻塞在队列上,当生产者向队列中写入数据后,线程池中的某个线程会被唤醒,该线程从队列中取出上述结构体(或对象),以其中的数据作为参数并调用处理函数。

12.线程池中线程数量

具体从处理任务所需资源分析,对于CPU密集型(也就是处理任务不需要外部IO,计算较多耗时长,只需要线程数量与CPU的核数基本相同就可以),而对于I/O密集型(计算部分耗时不多,大部分时间在磁盘或者网络I/O)需要使用测试工具评估在I/O上等待时间WT,以及CPU计算所需时间CT,对于N核系统所需N*(1 + WT/CT),大概需要2N个线程才能充分利用CPU资源。

13.协程vs线程

主要区别在调度开销上。

线程是被内核所调度,线程被调度切换到另一个线程上下文的时候,需要保存一个用户线程的状态到内存,恢复另一个线程状态到寄存器,然后更新调度器的数据结构,这几步操作设计用户态到内核态转换,开销比较多。

而协程的调度完全由用户控制,协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作用户空间栈,完全没有内核切换的开销。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值