腾讯 微信客户端-C++ 二面
1、你做的高性能日志系统里最有挑战的是什么
答案:高性能日志系统里最有挑战的部分通常不是把日志写出去,而是在高并发场景下同时兼顾吞吐、时延和落盘可靠性。
如果每条日志都直接加锁写文件,线程一多就会在锁和系统调用上卡住,所以一般会把日志写入分成前台生产和后台刷盘两段。前台线程只负责把日志快速写入缓冲区,后台线程批量落盘。这样可以把大量小写合并成少量顺序写,减少系统调用和磁盘抖动。
真正难的地方在于几个细节。第一是缓冲区切换时机,切得太频繁会增加刷盘压力,切得太慢又会拖高日志可见延迟。第二是满载场景下的回压策略,如果生产速度远大于消费速度,日志队列迟早会堆爆,这时要决定阻塞业务线程、丢弃低优先级日志,还是降级打印。第三是崩溃前的数据保全,如果进程异常退出,缓冲区里还有没刷出去的数据,要考虑刷盘策略、刷盘周期和可靠性之间怎么平衡。
如果继续做优化,还会把时间格式化、线程 ID 拼接、字符串拼装这些高频开销提前处理,减少真正写日志路径上的动态分配和重复格式化。
2、项目拷打
3、无锁队列在高并发下是怎么工作的
答案:无锁队列主要依赖原子操作维护共享状态。在并发环境下,多个线程不会通过互斥锁串行进入临界区,而是通过 CAS 去尝试推进 head 或 tail 指针。
如果是单生产者单消费者队列,结构会简单很多,因为 push 和 pop 的修改职责天然分开。生产者只推进 tail,消费者只推进 head,不需要复杂的竞争协调。如果是多生产者多消费者队列,就要考虑 ABA、内存回收、伪共享和内存序的问题。比如一个节点出队以后,不能立刻释放,因为其他线程可能还保留着旧指针;CAS 成功后,新写入的节点内容也必须对其他线程及时可见。
代码:
#include <atomic>
using namespace std;
template <class T>
struct Node {
T value;
atomic<Node*> next{nullptr};
Node(const T& v) : value(v) {}
};
template <class T>
class SPSCQueue {
public:
SPSCQueue() {
Node<T>* dummy = new Node<T>(T{});
head_ = tail_ = dummy;
}
void push(const T& v) {
Node<T>* node = new Node<T>(v);
tail_->next.store(node, memory_order_release);
tail_ = node;
}
bool pop(T& out) {
Node<T>* next = head_->next.load(memory_order_acquire);
if (!next) return false;
out = next->value;
delete head_;
head_ = next;
return true;
}
private:
Node<T>* head_;
Node<T>* tail_;
};
4、乐观锁怎么理解,mutex 和自旋锁分别适合什么场景
答案:乐观锁的思路是先假设并发冲突不严重,修改共享数据时再做校验。如果状态没变,就提交;如果变了,就重试或者回滚。CAS 就是这种思路最常见的底层实现。
mutex 更适合锁持有时间不确定、临界区可能较长、甚至可能阻塞的场景。线程拿不到锁时通常会被挂起,让出 CPU。自旋锁更适合临界区很短、锁能很快释放的场景。拿不到锁的线程不睡眠,而是在用户态循环等待,避免线程切换。
如果竞争比较激烈,自旋锁会浪费大量 CPU;如果临界区很短但访问特别频繁,mutex 又可能在调度开销上显得偏重。
5、线程切换时到底切了什么
答案:线程切换时,操作系统需要先保存当前线程的执行现场,再恢复下一个线程之前保存的现场。
这里面包括通用寄存器、程序计数器、栈指针、状态寄存器,还可能包括浮点寄存器、SIMD 寄存器以及线程局部存储相关状态。除了 CPU 现场,还要切换线程在调度器里的状态,比如运行态、阻塞态、时间片、优先级等。
如果是同一个进程内的线程切换,地址空间一般不变;如果是不同进程之间切换,还会涉及页表切换和 TLB 干扰,代价更高。
6、栈上的对象是怎么析构的
答案:栈上对象的生命周期由作用域控制。编译器在生成代码时,会把析构调用插入到作用域结束的位置。正常路径上,执行流走到作用域末尾时,会按对象构造的逆序调用析构函数。
如果中途抛异常,编译器会配合异常展开机制,在栈展开过程中自动调用已经构造成功对象的析构函数,所以局部对象在异常场景里也能正常释放。
代码:
#include <iostream>
using namespace std;
class A {
public:
A(const char* name) : name_(name) {
cout << "construct " << name_ <<
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.
