记录一次面试经历(深刻)
最近萌生了找工作的想法,于是在网站上挂出了简历,也顺利接到了一些公司的面试邀请。今天刚从一家公司(简称NYT吧)面试回来,打算将面试过程记录下来,做个总结,也算是提醒自己。
面试流程
晚上六点半的时候,我趁着下班间隙前往NYT公司,那边的软件部负责人带我到一个小型会议室面试。
他首先拿着我的简历询问了我之前工作上的事,问我之前的项目经历。我简单的和他说明了一下,说自己主要从事C++软件的开发,负责过一些模块,其中大部分是跟界面和UI相关的(汗,为什么一从我说的如此简单,并给人感觉low呢。。。)
面试官后面问了我简历上关于研究生时代的项目,我也几句话带过了(感觉自己犯了大忌啊)。
面试题目
面试官看我这项目上没啥好问的了,就打算考考我的基础知识,然后他提了两个问题。接下来的半个小时我将毕生难忘。
第一个问题:
如图所示
有十台机器M1~M10,分别以100赫兹的频率向中间的服务器S发送一个位置坐标信息
(x,y,z)
,中间的服务器将发送过来的100个数据取平均
然后将该数据分发给100台电脑 P1~P100。 问我,如果让我来做,这个系统该怎么弄?
好吧,我感觉有点懵,但不能让人看出来啊。
考虑一下,问发送的网络有延迟么,回答没有。
问是不是发送过程中可能产生丢包,需要进行数据验证,如果需要数据验证的话,就每一百个数据缓存一下,然后计算它们的md5值发送过去,让接收的机器隔100个数据进行一次校验啥的。面试官期望的不是这个,就说,网络保证能发送到。
那到底要让我说啥啊,我表示投降了,直接问面试官,这个题到底想考我啥,他看我一直说不到点上,就提醒我是生产者和消费者。
哈哈,前一阵子就刚看过一篇文章,讲的是java的生产者和消费者模式的实现,而且本身也对生产和和消费者模式有一定的了解。我就直接说使用一个缓冲队列来弄。面试官还是不满意,问该怎么弄啊。我说假设接收P1~P100接收不及时,可以先将数据放入缓冲队列中,然后blabla。这时面试官又跟我说这些都不考虑,假设它们接收数据是立即能够处理的,包括服务器求平均也不考虑时间花销的。
接下来我就产生疑问了,如果所有的都这么理想化的话,那感觉没必要用什么消费者生产者模式啊,直接发送就行了。哦,对了,M1~M10向服务器发送这里需要有个开关保证10个机器的数据都收到了,然后才能进行求平均计算啊。可以在服务器上加上缓冲队列啊。然后就简单比划了一下。
第二题:
假设有一个空间,已有一个人,一条狗,一只老鼠和一只兔子,问该怎么往里面加入一只鸟和一条鱼,并且鸟是会飞的、会歌唱的,鱼是会有水的、会说话的。
我听到这个问题感觉更懵了,这都问得啥啊?差点要脱口而出,那就直接加进去不就得了。
然后我模模糊糊的说了在这是不是对应程序中的类对象,如果要加的话就参考原来类对象的结构,将新的鸟对象和鱼对象加进去就行了。构造两个类,同时添加飞行,唱歌等方法,并且可以的话复用原有的方法,比如狗对象,或者人对象的方法拿过来用。
好了,这道应该不算太难,并且能够轻易展开的题就这么被我浪费了。。。
而后面试官看我俩老是聊不到一块,就结束了面试。后面我询问了他问这两道题的意思。第一题的确是询问我消费者和生产者方面的内容,第二题就是考我面向对象的方面的理解。但这两题都没有标准答案,我可以任意发挥。此刻我恍然大悟。
总结
我感觉我错过了一次良好的机会,一开始我就将整个面试的方式理解错了。由于本身参加的面试次数少,而且之前都是笔试,面试(面试考的也是具体问题,具体技术),而这一回属于头一次碰上这种开放式形式的面试,我还是惯性的按照之前的思维和习惯行事,实在是太不应该了。
首先自己的在这方面经历的太少,很明显(我是走出公司大门才意识到),面试官是期望我主导整个面试流程,让我自己发掘问题,自己解决问题,而他在这个过程中可提问,可引导,从而判断我的能力。而我由于之前的习惯,经常是具体问题具体解答,模糊问题只会找出关键点进行简要分析。很少会针对一个模糊问题展开来说,其实就算让我展开来说,在没有准备的情况下,我也是讲不好的。
其次,我的表达能力不行。这是由于平常性格原因,表达的太少。问题常常是自己理解了,但在没有准备的前提下,想将问题同别人解释清楚,往往是比较费劲的。尤其在面试这种经常的情况下,我更难办到。而且平常我很少手写代码,所以在面试时,我也没考虑在纸上写出示例代码并且展开说明。
如上两点其实很致命,想象某个情况下,就给你只字片语几句话,然后让你上台讲上半个小时,该怎么办呢?这在以后的工作生活中很可能遇到的,这是你必须要讲出一些东西来,而且不能最后大家听下来都感觉在浪费时间才行啊。
最后,那就是我的技术基础的确不行。面试官最后说了一句,我们既然选择干这一行,就必须把这些基础都要掌握了。而我,之前自认为掌握的挺好,但通过这次面试,我明白了,我所谓的掌握其实有点自欺欺人。真正的掌握是能活学活用,是能准确无误表达出来,是能在需要的时候就能想到并且用到。
这次的收获真的不小。最后,用我女票送我的一句话结尾:“知耻而后勇!”
附(问题的再次解答)
第一题:
第一题是个多对一,再一对多的网络传输问题,明显适用于生产者和消费者模式。在不考虑,丢包,延迟,缓冲溢出的情况下,该问题变得很简单和清晰了。问题分两个阶段考虑
第一阶段
首先十台机器M1~M10以100Hz的频率发送数据,我们可以最简单的认为其有个时间循环,以0.01s的时间间隔,进行数据发送。将这个步骤简化如下
void OnTimer()
{
time.sleep(0.01);
send(x, y, z);
}
其次要在服务器端实现生产者和消费者模式,并且考虑求平均操作是在所有十台机器都数据都收到的前提下才可以进行。
服务器端开十个线程负责建立与十台机器的连接,并接收数据。分别有十个队列,缓冲接收的消息(不考虑溢出)。
typdef stuct Positions {
int x;
int y;
int z;
} Pos;
Queue<Pos> qm;
class Thread
{
\\...
void run{
while(true) {
int x, y, z;
receive(x, y, z);
Pos pp(x,y,z);
qm.enqueue(pp);
}
}
\\...
};
然后服务器端有个主线程,也能访问这些队列。如果这些队列同时有数据,则取出数据,取平均。考虑效率,主线程的循环以0.01s进行一次为佳。
void main()
{
int X[10];
int Y[10];
int Z[10];
while(true) {
time.sleep(0.01);
if(all_data_ready()) { //判断所有数据是否准备齐全
read_all_data(X, Y, Z);
compute_average(X, Y, Z);
send_all(X, Y, Z); //发送给100台机器
}
}
}
read_all_data() 的实现是将队列的数据都dequeue,然后放入到X,Y,Z数组中去。
其中关键是如何判断所有数据都准备完毕,即函数 all_data_read() 的实现。
简单的实现就是,直接判断十个队列中是否有数据。
这样第一阶段就完成了,实现非常low。
尝试改进下,可以通过10个位(2个字节,用unsigned short变量即可)来标记队列数据是否准备好。all_data_read()中就直接判断10个位是否都为1。而接收线程获取到数据后,顺便将对应位置1(无需判断原来的状态)。主线程read_all_data()中从各个队列中读取数据时,顺便判断队列中是否还存在数据,如果不存在则将对应位置0。
如此效率可能会高一点。
既然使用了多线程,我猜想必然会进一步考到多线程的锁机制。刚才的方式是否可以通过线程锁的方式来改进呢?
在前面描述的利用标记变量的方法中,read_all_data()判断队列是否还存在数据,不存在将对应位置0的操作最好加锁。否则如果判断不存在了,接收线程这时接收到数据,然后在置0,会导致必须要再次接收数据才会执行下面的运算。所以这个判断+置0操作最好加锁。
但不用标记变量,直接加锁是否可行?感觉不像典型的线程锁使用方式,暂时想不到什么好办法。
目前想到的方法就是,再添加一个监控线程,专门负责监控数据是否准备完毕。由监控线程(抽象为生产者)和主线程(消费者)之间进行普通锁的竞争。具体就略过了。。。
第二阶段:
第二阶段其实并没有什么难点,对于每个PC而言,都是一对一的生产者和消费者。直接进行接收就行了。可以考虑的是,服务器如何保证100台机器都收到了数据,收到数据是否一致等等。
第二题:
第二题理论上应该更简单,的确是靠面向对象的问题的,我需要更加展开的说。并且可以在纸上将类图绘制出来。
假设原来的世界所有的对象都继承自Animal类,Animal有公共方法breath。
现在想要加入鸟和鱼,首先应该想到的是它们生活空间不一样。为了整体的统一,可以考虑修改之前的类,加入一个中间对象。最后类图如下
如果再进一步的话,就要考虑世界中其它对象的建模了。比如树啊,水啊,云彩等啥的。这时候再设定一个最基类Object,从此继承出来Animal对象和非Animal对象会比较合适。Object对象可以进行一些生命周期管理,问题诊断啥的工作。