影石 音视频开发-C++ 二面
1. 你能实习多久,能不能稳定实习 6 个月
2. TCP 丢包之后一般怎么处理
答案:TCP 是可靠传输协议,所以它不会把“丢包”直接暴露给应用层,而是通过确认、重传、滑动窗口这些机制自己处理。最基本的手段是超时重传,也就是发送方在发出数据后等待 ACK,如果超时没收到确认,就认为可能丢包并重传。除此之外还有快速重传。如果发送方连续收到多个重复 ACK,说明某个中间报文段大概率丢了,不必等超时,直接提前重传。在工程上,丢包不只是“重传一下”这么简单,它还会影响 RTT 估计、拥塞控制、发送窗口增长和整体吞吐。如果是在音视频场景里,还要补一句:TCP 虽然可靠,但丢包重传会带来时延抖动,所以实时音视频很多时候更倾向 UDP + 自定义重传、FEC 或者拥塞控制方案,而不是纯 TCP。
3. 讲一下拥塞窗口,怎么调整,有哪些算法
答案:拥塞窗口 cwnd 可以理解成发送方基于网络拥塞状态给自己加的一层流量限制。发送方真正能发多少数据,通常取决于 min(接收窗口, 拥塞窗口)。接收窗口解决的是接收端来不来得及收,拥塞窗口解决的是网络能不能承受。TCP 拥塞控制经典过程包括慢启动、拥塞避免、快速重传和快速恢复。刚开始时通过慢启动快速探测网络能力,cwnd 近似指数增长;达到慢启动阈值后进入拥塞避免,改成线性增长;如果出现丢包,说明网络可能拥塞,就减小窗口。经典算法包括 Tahoe、Reno、NewReno,后面还有 CUBIC、BBR。CUBIC 在 Linux 上很常见,它针对高带宽长时延网络做了更激进的窗口增长;BBR 则不再主要依赖“丢包等于拥塞”这个假设,而是尝试建模带宽和最小 RTT。如果面试官继续追问音视频场景,可以顺手提一下:实时流媒体更关注时延和抖动,很多时候会借鉴 TCP 拥塞控制思想,但不会完全照搬。
4. 进程间 IPC 里最快的通常是什么,共享内存怎么同步
答案:如果只讨论同机 IPC 的纯数据传输效率,共享内存通常是最快的一类方案,因为它避免了多次数据拷贝,多个进程直接看到同一块物理内存。但共享内存快,不代表它“开箱即用”。它只解决了数据共享问题,不解决并发同步问题。同步常见做法包括进程间互斥锁、读写锁、POSIX 信号量、System V 信号量、原子变量、环形队列加序号控制等。如果是单生产者单消费者,通常可以设计成无锁或低锁结构;如果是多生产者多消费者,就要更仔细地处理竞争、缓存一致性和伪共享问题。所以 IPC 里真正难的不是“哪种最快”,而是“哪种在你的访问模式下吞吐高、延迟稳、实现复杂度可控”。
5. 在共享内存上直接分配 vector 会有什么问题
答案:直接把普通 std::vector 放到共享内存里,通常会有问题。核心原因是 vector 内部真正存数据的那块连续内存,默认还是通过当前进程的分配器去申请的,也就是进程自己的堆,不一定在共享内存区域内。这样就会出现一种很尴尬的情况:vector 对象本身放在共享内存里,但它内部的 data 指针指向的是某个进程私有地址空间,其他进程根本没法正确访问。另外即便数据本身也放到共享内存里,普通 STL 容器内部常常依赖绝对地址、默认 allocator、进程内对象语义,跨进程直接共享并不安全。更合理的做法是使用共享内存专用分配器、偏移指针,或者直接自己设计定长数组 / 环形缓冲区结构。如果面试官追问,可以进一步答 Boost.Interprocess 这类库,它就是专门解决这类共享内存容器问题的。
6. 用事件驱动模型实现一个定时器,思路是什么
答案:事件驱动模型下实现定时器,核心思路不是“开一个线程 sleep”,而是把时间事件纳入统一事件循环。通常会维护一个按触发时间排序的数据结构,比如小根堆、时间轮、红黑树。事件循环每次处理 I/O 事件之前,会先看当前最近一个定时器还有多久到期,然后把这个时间作为 epoll_wait 或 poll/select 的超时时间。如果超时返回或者有其他事件唤醒,就检查哪些定时器到期,把回调执行掉。小根堆适合通用定时器实现,插入和取最近到期元素都比较直接;时间轮更适合大量定时任务且精度要求可控的场景。在音视频开发里,定时器往往会用在心跳、帧调度、超时回收、重传检测、播放时钟同步这些地方。
代码:
#include <iostream>
#include <queue>
#include <vector>
#include <chrono>
#include <thread>
#include <functional>
using namespace std;
using Clock = chrono::steady_clock;
struct TimerTask {
Clock::time_point expire;
function<void()> cb;
bool operator<(const TimerTask& other) const {
return expire > other.expire; // 小根堆
}
};
int main() {
priority_queue<TimerTask> pq;
pq.push({Clock::now() + chrono::seconds(1), [] {
cout << "task1 fired\n";
}});
pq.push({Clock::now() + chrono::seconds(3), [] {
cout << "task2 fired\n";
}});
while (!pq.empty()) {
auto now = Clock::now();
auto next = pq.top().expire;
if (now < next) {
this_thread::sleep_for(next - now);
}
while (!pq.empty(
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.

查看9道真题和解析