同程旅行 - C++ 开发工程师 - 一面(春招第三面)
全程将近一小时,会议上写着预计半小时。面试官整体很耐心地再引导我回答问题,部门是做中间件代理相关的。八股和项目都拷打的非常细致,能感觉出来面试官提问有倾向性,基本都是问挂掉了怎么办?出现异常怎么办?没有单纯八股文问答,感觉整体面试很专业,没啥水分,干货满满。
技术面经
C++:
- new 和 malloc 的区别
- new 出来的对象可以用 free 释放吗(如果是 int 类型指针呢)
- 一个 int 数组可以用 delete 命令释放么(不可以、int 数组采用
new[]
分配、应用delete[]
释放) - 释放一个数组为什么非要用
delete[]
、delete 命令不行吗 - C++ 有其他方式可以更好地管理数组资源么(容器 vector、智能指针、RAII)
- 智能指针有哪几种类型
- 什么情况下需要使用 weak_ptr
- 如何去调用 weak_ptr 指向的对象及其方法(先将 weak_ptr 转化为 shared_ptr 再调用)
Golang:
- 使用 channel 可能会有哪些隐患(死锁 & 阻塞)
- 针对一个无缓冲 channel,先启动一个 goroutine 向 channel 中不断写入数据,再挂起一个 goroutine 从 channel 中不断读取数据,此时程序会发生什么?如果是一个大小为 4 的有缓冲 channel,一开始只向 channel 中写入一个数据,后面读取程序相同,程序又会如何?
- 使用
for i, v := range arr
语句遍历对象数组时,变量v
中保存的是对象的引用还是值拷贝? - 如果想通过
for ... range
将一个数组的值拷贝到另一个数组,数组拷贝后的结果是什么?
m := make(map[int]*int) arr := []int{1, 2, 3, 4, 5} for i, v := range arr { m[i] = &v // v 只做值拷贝, 相当于 v 每次都保存相同的地址 } for k, v := range m { fmt.Println(k, *v) // *v 全部是最后一个元素的数值 5 }
Project:
- jiyun-ns 你主要的工作内容和难点
- jiyun-ns 中你的程序通过何种方式与 etcd 进行通信(gRPC)
- dag-engine 中如何实现 Redis 分布式锁(SETNX + 过期时间 + 续费机制)
- dag-engine 中如果工作线程挂掉,没有关掉 channel,其他线程只能等待锁过期后再获取锁吗?同样情况下,执行续费操作的 goroutine 会一直续费吗?(潜在 bug、可以考虑使用 defer 语句关闭 channel)
Webserver:
- 主线程和工作线程分别做哪些事情
- 如果客户端发送请求后网络异常,你的服务器如何响应?能否感知到网络异常?(超时重试 & 心跳机制)
- 如何处理过大的 HTTP 请求、是否出现让两个工作线程分别读取一部分消息(HTTP/1.1 请求未拆分)
- 如何通过 Epoll 获取到活跃事件、并唤醒具体工作线程处理事件(
pool->cond.wait(locker)
) - 对于被标记为活跃状态但实际上没有能力执行我方业务逻辑的 Socket、你有什么方法判别(业务逻辑验证)
- 服务器如何与 MySQL 建立连接、使用哪个库和库函数(
mysql.h
) - 如何使用什么函数读取消息(
readv
实现分散读取、缓冲区大小为 65535 bit)
ssize_t Buffer::ReadFd(int fd, int* saveErrno) { char buff[65535]; struct iovec iov[2]; const size_t writable = WritableBytes(); iov[0].iov_base = BeginPtr_() + writePos_; iov[0].iov_len = writable; iov[1].iov_base = buff; iov[1].iov_len = sizeof(buff); const ssize_t len = readv(fd, iov, 2); if(len < 0) { *saveErrno = errno; } else if(static_cast<size_t>(len) <= writable) { writePos_ += len; } else { writePos_ = buffer_.size(); Append(buff, len - writable); } return len; }
- 使用
readv
读取消息完成后会收到什么提醒