得物 客户端开发-C++ 一面
1. TCP 和 UDP 的区别
答案:TCP 是面向连接、可靠传输、字节流、有拥塞控制和流量控制的协议,适合对数据正确性要求高的场景。UDP 是无连接、不保证可靠、不保证顺序、报文边界天然保留,开销更小,时延通常更低。TCP 更像“把数据安全送到”,UDP 更像“尽快发出去”。如果继续往深里问,TCP 还会涉及三次握手、四次挥手、滑动窗口、重传、拥塞控制这些,而 UDP 常被拿来做实时通信、服务发现、音视频或自定义可靠传输。
2. 什么场景下使用 UDP
答案:UDP 适合更关注实时性、容忍少量丢包、需要自己掌控传输策略的场景。典型像音视频通话、在线游戏状态同步、局域网广播发现、DNS 查询、监控上报、日志采样这些。因为 UDP 没有连接建立和复杂确认机制,所以更轻,但也意味着丢包、乱序、重复包这些都要上层自己处理。真正工程里不是“UDP 更快就用 UDP”,而是看业务到底更怕延迟还是更怕出错。
3. 进程和线程的区别
答案:进程是资源分配的基本单位,线程是调度的基本单位。进程有独立地址空间、页表、文件描述符上下文等,线程共享所属进程的大部分资源,但有自己的栈、寄存器上下文和线程局部存储。进程之间隔离更强,稳定性更好;线程之间通信更直接,但同步问题更多。如果继续深入,面试官通常会问线程切换为什么比进程切换轻、线程共享哪些资源、线程安全怎么保证。
4. 线程调度方式有哪些
答案:从操作系统角度看,常见有时间片轮转、优先级调度、抢占式调度、实时调度等。普通 Linux 进程大多运行在 CFS 这类公平调度模型下,核心目标是尽量让各线程获得相对公平的 CPU 时间。如果是实时线程,还会涉及 SCHED_FIFO、SCHED_RR 这类策略。线程调度并不是“谁先创建谁先跑”,而是内核综合优先级、运行时间、等待时间、调度策略一起决定的。
5. 为什么线程切换开销通常比进程切换小
答案:线程切换通常不需要切换完整的虚拟地址空间,同一进程内的线程共享页表和大部分进程资源,所以相比进程切换,代价通常更小。但它也不是零成本,仍然要保存和恢复寄存器、程序计数器、栈指针,还可能导致 cache 失效和锁竞争。真正影响性能的很多时候不是“线程轻”,而是线程过多导致频繁切换,最后调度成本、锁争用成本比实际业务逻辑还高。
6. 进程间通信有哪些,怎么选
答案:常见 IPC 有管道、命名管道、消息队列、共享内存、信号、socket、信号量、mmap。如果是本机高吞吐低延迟,通常优先考虑共享内存;如果要跨机器,基本离不开 socket;如果是简单父子进程通信,管道已经够用;如果需要消息边界和解耦,消息队列更自然。选型时不能只看性能,还要看编程复杂度、同步成本、是否跨机、是否易于排障和扩展。
7. select、poll、epoll 的区别
答案:select 有 fd 数量上限,每次调用都要把 fd 集合从用户态拷到内核态,还要线性扫描。poll 去掉了固定上限,但本质上仍然是遍历。epoll 把“注册关注的 fd”和“等待就绪事件”拆开了,适合大量连接场景,不需要每次都全量传递和扫描所有 fd。所以高并发网络程序里更常说 epoll,但不代表任何场景都必须用它,小规模 fd 下差距并没那么夸张。
8. epoll 的 LT 和 ET 模式有什么区别
答案:LT 是水平触发,只要 fd 还处于可读或可写状态,下次 epoll_wait 仍然会通知。ET 是边缘触发,只在状态发生变化时通知一次。ET 通常要配合非阻塞 IO 使用,而且读写时要尽可能处理到返回 EAGAIN,否则容易漏事件。LT 更稳、更容易写对,ET 事件通知更少,但代码要求更严格。这题往下追一般会问:为什么 ET 要配非阻塞,以及如果一次没读完会发生什么。
9. STL 容器线程安全吗
答案:大多数 STL 容器都不是“线程安全容器”。多个线程同时读同一个容器,在没有写入的前提下通常问题不大;但一旦有线程写,哪怕另一个线程只是读,也可能出现数据竞争、迭代器失效甚至未定义行为。所以 STL 的线程安全要分开看:容器本身不帮你做同步,线程安全需要业务层自己保证。如果面试官继续问,通常会延伸到 shared_ptr 的线程安全、无锁结构、读写锁和迭代器失效。
10. 多线程在日常开发里会怎么使用
答案:比较常见的是线程池执行异步任务、生产者消费者模型、日志异步落盘、后台定时任务、网络收发线程和业务处理线程分离。在工程里,多线程不是为了“显得高级”,而是为了把阻塞操作和耗时任务从主线程拆出去,提高吞吐或者保证响应性。但线程越多不一定越快,线程模型设计不好,很容易变成锁竞争、上下文切换和时序 bug 的温床。
11. 线程池的核心设计点有哪些
答案:线程池最核心的是任务队列、工作线程管理、拒绝策略、关闭流程和异常处理。再往深一点,还要考虑任务是否有优先级、是否支持定时任务、线程数如何动态伸缩、队列满了怎么办、任务执行超时如何感知。真正可用的线程池不只是“开几个线程循环取任务”,还要把生命周期和边界情况设计完整。
代码:
#include <iostream>
#include <thread>
#include <queue>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <functional>
using namespace std;
class ThreadPool {
public:
ThreadPool(size_t n) : stop_(false) {
for (size_t i = 0; i < n; ++i) {
workers_.emplace_back([this] {
while (true) {
function<void()> task;
{
unique_lock<mutex> lock(mtx_);
cv_.wait(lock, [this] {
return stop_ || !tasks_.empty();
});
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.
查看10道真题和解析