
1) 【一句话结论】在C++实现高并发请求处理模块时,核心采用线程池+任务队列的生产者消费者架构,通过条件变量与互斥锁保障任务队列同步,结合读写锁优化用户登录等读多写少场景(内存锁与数据库锁按顺序获取避免死锁),并依据压力测试数据动态调整线程池阈值(如队列积压或空闲率超过阈值时扩缩容),任务队列满时按业务优先级(关键业务阻塞,非关键丢弃)处理,平衡性能与资源。
2) 【原理/概念讲解】老师口吻解释关键概念:
3) 【对比与适用场景】
| 对比维度 | 策略/类型 | 定义 | 特性 | 使用场景 | 注意点 |
|---|---|---|---|---|---|
| 线程池线程数 | 固定大小 | 预先创建固定数量线程 | 线程数固定,资源占用稳定 | 请求量稳定 | 可能线程数不足或过剩 |
| 线程池线程数 | 动态调整 | 根据负载(队列长度、空闲率)增减线程 | 线程数自适应 | 请求量波动大 | 需监控指标,增加复杂度 |
| 锁类型 | 互斥锁 | 独占访问 | 读/写都阻塞其他线程 | 写操作少,读操作多 | 读线程等待写线程,性能下降 |
| 锁类型 | 读写锁 | 读共享,写独占 | 读多写少场景 | 用户登录(读多写少) | 写时阻塞读,读时允许写;需协调数据库锁避免死锁 |
| 锁类型 | 自旋锁 | 线程循环等待 | 锁竞争轻时 | 短时间持有锁 | CPU占用高,锁竞争重时性能差 |
| 任务队列 | 无容量限制 | 队列无大小限制 | 生产者无阻塞 | 请求量极低 | 可能导致内存溢出 |
| 任务队列 | 有容量限制 | 队列设置最大容量 | 生产者阻塞或丢弃任务 | 请求量波动大 | 需根据业务重要性选择策略(关键业务阻塞,非关键丢弃) |
| 无锁实现 | CAS等 | 无锁操作,线程安全 | 高并发读,无锁竞争 | 纯内存操作的高并发读场景 | 需保证数据一致性(重试次数有限制),不适合涉及数据库的写操作 |
4) 【示例】(伪代码,含动态调整与锁协同)
// 动态线程池类(含同步原语)
class DynamicThreadPool {
private:
std::queue<std::function<void()>> taskQueue;
std::vector<std::thread> threads;
std::atomic<bool> isRunning{true};
std::mutex queueMutex;
std::condition_variable cv;
size_t minThreads = 4;
size_t maxThreads = 32;
size_t currentThreads = 8;
std::atomic<size_t> idleThreads{0};
void adjustThreadCount() {
size_t queueSize = taskQueue.size();
size_t idle = idleThreads.load();
if (queueSize > 100 && currentThreads < maxThreads) {
size_t newThreads = std::min(currentThreads + 2, maxThreads);
for (size_t i = currentThreads; i < newThreads; ++i) {
threads.emplace_back([this]() {
while (isRunning) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queueMutex);
cv.wait(lock, [this] { return !taskQueue.empty() || !isRunning; });
if (!isRunning && taskQueue.empty()) return;
task = std::move(taskQueue.front());
taskQueue.pop();
}
task();
}
});
currentThreads++;
}
} else if (idle > 0.5 * currentThreads && currentThreads > minThreads) {
size_t newThreads = std::max(currentThreads - 2, minThreads);
for (size_t i = currentThreads; i > newThreads; --i) {
threads[i - 1].join();
threads.pop_back();
currentThreads--;
}
}
}
public:
DynamicThreadPool(size_t initThreads = 8) : currentThreads(initThreads) {
for (size_t i = 0; i < initThreads; ++i) {
threads.emplace_back([this]() {
while (isRunning) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queueMutex);
cv.wait(lock, [this] { return !taskQueue.empty() || !isRunning; });
if (!isRunning && taskQueue.empty()) return;
task = std::move(taskQueue.front());
taskQueue.pop();
}
task();
}
});
}
}
~DynamicThreadPool() {
isRunning = false;
cv.notify_all();
for (auto& t : threads) t.join();
}
void addTask(const std::function<void()>& task) {
{
std::lock_guard<std::mutex> lock(queueMutex);
taskQueue.push(task);
}
cv.notify_one();
adjustThreadCount();
}
};
// 用户登录处理(读写锁+数据库锁协同)
void handleLogin(const std::string& username, const std::string& password) {
std::shared_mutex userMutex;
// 读操作(内存+数据库读)
std::shared_lock<std::shared_mutex> readLock(userMutex);
// 查询数据库:用户是否存在且密码正确(数据库读锁)
// 写操作(内存+数据库写)
std::unique_lock<std::shared_mutex> writeLock(userMutex);
// 更新数据库:标记登录状态(数据库写锁)
// 锁获取顺序:读时先内存共享锁,再数据库读锁;写时先内存独占锁,再数据库写锁,避免死锁
}
int main() {
DynamicThreadPool pool(8);
for (int i = 0; i < 1000; ++i) {
pool.addTask([i]() {
handleLogin("user" + std::to_string(i), "password");
});
}
return 0;
}
5) 【面试口播版答案】(约90秒)
“面试官您好,针对高并发请求处理模块,我的核心思路是构建线程池+任务队列的生产者消费者架构,通过条件变量与互斥锁保障任务队列同步,结合读写锁优化用户登录等读多写少场景,并依据压力测试数据动态调整线程池阈值(如队列积压或空闲率超过阈值时扩缩容),任务队列满时按业务优先级(关键业务阻塞,非关键丢弃)处理。具体来说,线程池预先创建线程,任务队列存储请求,线程从队列取任务执行。对于用户登录,查询用户信息(读)用共享锁,更新登录状态(写)用独占锁,与数据库锁协同时按顺序获取(读:内存共享锁→数据库读锁;写:内存独占锁→数据库写锁),避免死锁。动态调整线程池时,当任务队列长度超过100个任务且持续5秒,或空闲线程超过当前线程数的50%且持续3秒,就增减线程,平衡资源与性能。任务队列设置容量限制,队列满时,登录等关键业务会阻塞生产者等待队列空后再处理,避免请求丢失;课程预约等非关键业务则丢弃任务,减少资源占用。这样既能保证高并发下的性能,又能优化资源使用。”
6) 【追问清单】
7) 【常见坑/雷区】