面试准备(一)

1.i++是不是原子操作,++i呢?(属于多线程方面的问题)

i++确实不是原子操作,++i也不是原子操作。

(1)什么是操作系统的“原子操作”

原子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断,分为两种情况(两种都应该满足)

a. 在单线程中, 能够在单条指令中完成的操作都可以认为是" 原子操作",因为中断只能发生于指令之间。

B.在多线程中,不能被其它进程(线程)打断的操作就叫原子操作。

(2)i++在底层实现时分三步进行:

  1. 读内存到寄存器;
  2. 在寄存器中自增;
  3. 写回内存。

所以i++不是原子操作,上面的三个步骤中任何一个步骤同时操作,都可能导致i的值不正确自增。

(3)++i

在多核的机器上,cpu在读取内存i时也会可能发生同时读取到同一值,这就导致两次自增,实际只增加了一次。

2.红黑树查找时间复杂度?哈希查找的时间复杂度?

红黑树查找:O(logn)

哈希查找:O(1)

3.哈希一个字符串,输出的结果是什么?

hash算法是把内容转化为地址的一种算法,字符串hash可以理解为把字符串转化为一个数字。这个转化应当遵循同样内容的字符串必须转化为同一个数字,不同的字符串尽量转化为不同的数字。此外,数字的范围不应该太大。

4.三次握手后建立的连接就可靠吗?

(1)为什么不是两次握手(防止已失效的连接请求又传送到服务器端,因而产生错误)

假设改为两次握手,client端发送的一个连接请求在服务器滞留了,这个连接请求是无效的,client已经是closed的状态了,而服务器认为client想要建立一个新的连接,于是向client发送确认报文段,而client端是closed状态,无论收到什么报文都会丢弃。而如果是两次握手的话,此时就已经建立连接了。服务器此时会一直等到client端发来数据,这样就浪费掉很多server端的资源。

(2)三次握手的最主要目的是保证连接是双工的,可靠更多的是通过重传机制来保证的(即超时重传机制)。

TCP可靠传输的实现:

TCP 连接的每一端都必须设有两个窗口--->一个发送窗口和一个接收窗口。TCP 的可靠传输机制用字节的序号进行控制。TCP所有的确认都是基于序号而不是基于报文段。发送过的数据未收到确认之前必须保留,以便超时重传时使用。发送窗口没收到确认不动,和收到新的确认后前移。

发送缓存用来暂时存放:发送应用程序传送给发送方 TCP 准备发送的数据;TCP 已发送出但尚未收到确认的数据。

接收缓存用来暂时存放:按序到达的、但尚未被接收应用程序读取的数据; 不按序到达的数据。

5.多线程和多进程哪个好?什么情况下用多进程?

没有最好,只有更好。根据实际情况来判断,哪个更加合适就是哪个好。属于开放性试题,不要被面试官的问法带进坑,片面的说自己认为哪个好。

进程是资源分配的最小单位,线程是CPU调度的最小单位。

 

 

1)需要频繁创建销毁的优先用线程

原因请看上面的对比。

这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的

2)需要进行大量计算的优先使用线程

所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。

这种原则最常见的是图像处理、算法处理。

3)强相关的处理用线程,弱相关的处理用进程

什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。

一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。

当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。

4)可能要扩展到多机分布的用进程,多核分布的用线程

原因请看上面对比。

5)都满足需求的情况下,用你最熟悉、最拿手的方式

至于“数据共享、同步”、“编程、调试”、“可靠性”这几个维度的所谓的“复杂、简单”应该怎么取舍,我只能说:没有明确的选择方法。但我可以告诉你一个选择原则:如果多进程和多线程都能够满足要求,那么选择你最熟悉、最拿手的那个。 

需要提醒的是:虽然我给了这么多的选择原则,但实际应用中基本上都是“进程+线程”的结合方式,千万不要真的陷入一种非此即彼的误区。

消耗资源:

从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

通讯方式:

进程之间传递数据只能是通过通讯的方式,即费时又不方便。线程时间数据大部分共享(线程函数内部不共享),快捷方便。但是数据同步需要锁对于static变量尤其注意

线程自身优势:

提高应用程序响应;使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上;

改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

6.fork()后子进程和父进程的返回值分别是什么?

那么fork产生子进程的表现就是它会返回2次,一次返回0,顺序执行下面的代码。这是子进程。

一次返回子进程的pid,也顺序执行下面的代码,这是父进程。

补充:

fork后,子进程会复制父进程的task_struct结构,并为子进程的堆栈分配物理页。理论上来说,子进程应该完整地复制父进程的堆,栈以及数据空间,但是2者共享正文段。

关于写时复制:由于一般 fork后面都接着exec,所以,现在的 fork都在用写时复制的技术,顾名思意,就是,数据段,堆,栈,一开始并不复制,由父,子进程共享,并将这些内存设置为只读。直到父,子进程一方尝试写这些区域,则内核才为需要修改的那片内存拷贝副本。这样做可以提高 fork的效率。

 

关于线程的堆栈:

生成子线程后,它会获取一部分该进程的堆栈空间,作为其名义上的独立的私有空间。(为何是名义上的呢?)由于,这些线程属于同一个进程,其他线程只 要获取了你私有堆栈上某些数据的指针,其他线程便可以自由访问你的名义上的私有空间上的数据变量。(注:而多进程是不可以的,因为不同的进程,相同的虚拟地址,基本不可能映射到相同的物理地址)。

7.(情景题)有10000个int型数据(数据有可能重复),找出最大的前10个,算法的时间复杂度如何?

注(zzg的见解):这个问题关系到数据量的问题,如果要排序的话,属于外排序的问题。解决办法:

  1. 将数据进行hash
  2. 根据问题选择合适的算法(这里采用堆排序)

在大规模数据处理中,经常会遇到的一类问题:在海量数据中找出出现频率最好的前k个数,或者从海量数据中找出最大的前k个数,这类问题通常被称为top K问题。例如,在搜索引擎中,统计搜索最热门的10个查询词;在歌曲库中统计下载最高的前10首歌等。

 针对top K类问题,通常比较好的方案是分治+Trie树/hash+小顶堆(就是上面提到的最小堆),即先将数据集按照Hash方法分解成多个小数据集,然后使用Trie树活着Hash统计每个小数据集中的query词频,之后用小顶堆求出每个数据集中出现频率最高的前K个数,最后在所有top K中求出最终的top K。

 

 

玲儿加油,爱你么么哒