《C++ 项目:手写大模型接入 SDK》SessionManager会话管理模块:架构设计与实现

🔥小叶-duck:个人主页
❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《Linux系统从入门到实践》《Linux网络从入门到实践》
《Qt 方寸极境》 《MySQL》
✨未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游
目录
三、会话管理类头文件设计(SessionManager.h)
3.3.2 mutable std::mutex _mutex
3.3.3 std::atomic _sessionCounter
3.3.4 接口设计原则:全部基于 sessionId 操作
4.1.1 generateSessionId () 生成会话 ID
4.1.2 generateMessageId (size_t messageCount) 生成消息 ID
4.5 getSessionMessages 获取会话全部历史消息
4.6 getSessionLists 获取排序后的会话 ID 列表
4.7 updateSessionTimestamp 更新会话时间戳
前言
在前面章节中,我们已经完成了多厂商大模型 Provider 的抽象封装,实现了 DeepSeek、ChatGPT、Gemini、Ollama 等模型的全量与流式接口调用。但当前 SDK 还缺少会话上下文的统一管理能力:每次调用模型都需要外部手动组装消息数组、维护会话标识,会话的创建、消息追加、会话排序、过期清理等逻辑散落在业务代码里,复用性差,也很难保证多线程并发访问时的数据安全。
本章我们将独立实现一套
SessionManager会话管理模块,专门负责对话会话的生命周期管理。我们会从业务需求出发,先梳理会话模块需要解决的核心问题,再定义消息与会话基础结构体,接着完成头文件设计、逐一实现每一个成员接口,同时重点讲解存储结构、互斥锁、原子变量等线程安全相关设计。最后基于 GTest 编写单元测试,完整验证会话创建、消息保存、时间戳更新、会话排序、删除等全部能力。会话管理器是 SDK 向上层业务交付的重要组件,它和前面的 LLMProvider、LLMManager 配合,让上层业务只需要关心会话 ID,不用再手动维护对话上下文,让整个 SDK 的使用体验更加贴近真实对话产品。
一、LLM 会话管理模块整体介绍
会话定义:会话代表用户和大模型之间一组连续交互的集合,它承载本轮对话的全部消息、会话基础状态,保证多轮对话的连贯性,让模型能够读懂历史对话内容。
大模型侧的本质特性:所有我们对接的 DeepSeek、Gemini、Ollama 等大模型 API 都是无状态服务。模型服务端不会自动记住上一轮对话内容,上下文记忆完全由调用方 SDK 自行维护,每次发起对话请求,都必须把完整历史消息一并传给模型。
1.1 业务上需要解决的几个核心问题
- 多轮对话产生的多条消息,应该以什么样的结构存储;
- 模型本身没有记忆,每次请求都必须携带完整历史上下文,缺少统一管理就无法实现连续对话;
- 多用户、多对话场景下,如何区分一条消息归属哪一次对话;
- 在大量会话数据中,可以根据会话标识快速取出某次对话的全部历史消息,组装成请求参数传给 LLMManager。
之前我们在单元测试里,是手动往
messages数组追加 user、assistant 消息来模拟多轮对话,这种写法只适合简单验证接口,业务代码不能这样写。历史消息应当归属到会话对象里,由会话管理器统一维护,而不是上层业务手动拼接消息列表。

1.2 会话管理完整业务需求
基于 Session 结构体,我们的会话管理器需要对外提供一套完整能力,覆盖会话全生命周期:
- 创建会话:生成全局唯一
session_id,新建会话对象,绑定该会话使用的模型名称; - 根据
session_id查询对应的会话对象; - 向指定会话追加消息,支持 user 角色、assistant 角色消息写入;
- 获取指定会话内全部历史消息,直接组装成请求参数,交给 LLMManager 发送给大模型;
- 获取全部会话 ID 列表,用于前端 UI 展示历史会话列表;
- 根据 session_id 删除指定会话;
- 每次会话新增消息时,自动更新会话的最后更新时间戳;
- 根据
session_id删除指定的会话; - 支持清空全部会话;
- 查询当前管理器内一共存在多少个会话;
- 提供工具方法,生成全局唯一的会话标识
session_id; - 提供工具方法,生成每个会话中唯一的消息标识
message_id; - 保证线程安全:支持多线程并发对会话进行增、删、改、查操作,通过互斥机制保护共享资源。
1.3 线程安全约束
并发风险场景:
业务场景中经常会出现多用户同时访问 SDK,多个线程并行执行创建会话、追加消息、删除会话等操作。会话管理器内部保存的会话容器属于共享临界资源,并发访问会带来安全隐患。
会出现的风险:
- 并发生成 ID 时,出现
session_id或者message_id重复冲突; - 容器并发读写,迭代器失效、内部数据错乱;
- 内存数据损坏,程序出现崩溃、会话数据丢失。
对应的解决方案:
- 使用互斥锁
std::mutex保护内部存储会话的容器,所有读写容器的操作都要加锁; - ID 计数使用原子变量
std::atomic,保证 ID 自增过程无竞争; - 对于
const成员函数(比如查询会话、读取会话列表这类只读接口),锁成员变量需要加上mutable修饰,保证在 const 函数内部依然可以上锁解锁。
二、核心数据结构定义
本节我们将会使用之前在
common.h中定义会话模块所需要的基础结构体,这两个结构体是整个会话管理模块的数据载体,和前面 SDK 的消息结构保持统一,同时适配会话上下文存储需求。
2.1 Message 消息结构体(common.h)
Message 代表一条独立的聊天消息,和各大模型 API 中消息单元一一对应,用来承载单条对话的全部元数据与内容。
// 单条对话消息结构体,对应LLM接口message单元
struct Message
{
std::string _message_id; // 消息唯一ID
std::string _role; // 角色,如"user"、"assistant"、"system"
std::string _content; // 消息文本内容
std::time_t _timestamp; // 消息发送时间戳
// 构造函数,业务层传入角色与内容
Message(const std::string &role = "", const std::string &content = "")
: _role(role), _content(content)
{
}
};
结构说明
_message_id:每条消息独立唯一标识,方便后续检索、定位单条消息;_role:角色字段,对齐 OpenAI 风格 API 规范,支持 user 用户消息、assistant 模型回复、system 系统提示词;_content:消息正文,就是用户提问或者模型返回的回答文本;_timestamp:消息生成时间,用于消息排序、时间展示、日志追溯;- 构造函数提供默认参数,方便快速构造一条消息实例。
2.2 Session 会话结构体(common.h)
Session 代表一次完整的对话会话,一个会话绑定一个模型,内部有序保存本轮对话所有 Message 消息,用来维护多轮对话上下文。
// 会话信息:保存一轮对话的上下文与历史消息
struct Session
{
std::string _session_id; // 会话唯一ID
std::string _model_name; // 会话绑定使用的模型名称
std::vector<Message> _messages; // 会话内全部消息记录,有序存储
std::time_t _created_at; // 会话创建时间戳
std::time_t _updated_at; // 会话最后更新时间戳
// 构造函数:绑定本次会话使用的模型
Session(const std::string &model_name = "")
: _model_name(model_name)
{
}
};
结构说明
_session_id:会话全局唯一编号,是外部访问、查找、删除会话的主键;_model_name:记录该会话绑定的模型,一次会话全程使用同一个模型;_messages:消息数组,按对话先后顺序存放所有消息,发送给 LLM 时直接取出这个数组组装请求上下文;_created_at:会话创建时刻,用于会话列表展示、会话生命周期统计;_updated_at:最后一次追加消息的时间,每次新增消息后更新,用来排序会话列表(最近会话优先);- 构造函数支持在创建会话时直接指定模型名称。
三、会话管理类头文件设计(SessionManager.h)
前面我们已经定义好 Message、Session 基础数据结构,现在需要一个管理器统一负责会话的创建、查找、消息追加、删除等生命周期管理。SessionManager 是整个会话模块的核心调度类,负责维护所有会话实例,同时保证多线程环境下访问安全。
3.1 头文件依赖说明
#include <memory> // shared_ptr 智能指针,管理Session对象生命周期
#include <mutex> // 互斥锁,多线程保护会话共享资源
#include <unordered_map>// 哈希表存储会话,平均O(1)查询,适合sessionId检索
#include <atomic> // 原子变量,用于会话ID序号无锁自增
#include <algorithm> // sort排序,用于会话列表按更新时间排序
#include <sstream> // 字符串流,拼接格式化会话ID、消息ID
#include <iomanip> // 格式化输出辅助,配合sstream使用
#include "common.h" // 引入Message、Session结构体定义
unordered_map:采用哈希表,对比map红黑树,查询速度更快,我们的业务不需要会话有序存储,优先选择哈希结构;mutex:多线程场景下,多个线程同时读写会话容器,需要互斥锁保证线程安全;memory:std::shared_ptr托管堆上的 Session 对象,自动管理内存,避免内存泄漏;atomic:原子计数器,用于生成会话自增序号,支持无锁读取;algorithm:会话列表接口返回时,支持按照更新时间倒序排序;sstream+iomanip:字符串格式化工具,用来拼接生成唯一的sessionId、messageId;common.h:依赖我们在上一节定义的Message、Session结构体。
3.2 完整头文件代码
#ifndef SESSION_MANAGER_H
#define SESSION_MANAGER_H
#include <algorithm>
#include <atomic>
#include <iomanip>
#include <memory>
#include <mutex>
#include <sstream>
#include <unordered_map>
#include "common.h"
namespace ai_chat_sdk
{
class SessionManager
{
public:
// 创建会话:根据模型名称创建一个新的会话
std::string createSession(const std::string &modelName);
// 获取会话:根据会话ID获取会话对象的智能指针
std::shared_ptr<Session> getSession(const std::string &sessionId) const;
// 添加消息:将一条消息添加到指定会话的历史消息中
bool addMessage(const std::string &sessionId, const Message &message);
// 获取会话历史消息:根据会话ID获取指定会话的所有历史消息
std::vector<Message> getSessionMessages(const std::string &sessionId) const;
// 获取所有会话ID列表:返回所有当前存在的会话ID
std::vector<std::string> getSessionLists() const;
// 更新会话时间戳:根据会话ID更新指定会话的更新时间戳
void updateSessionTimestamp(const std::string &sessionId);
// 删除会话:根据会话ID删除指定会话
bool deleteSession(const std::string &sessionId);
// 删除所有会话:删除所有当前存在的会话
void deleteAllSessions();
// 获取会话总数:返回当前存在的会话总数
size_t getSessionsCount() const;
private:
// 生成会话唯一ID
std::string generateSessionId();
// 生成消息唯一ID
std::string generateMessageId(size_t messageCount);
private:
// 会话映射表:key:会话ID,value:会话对象的智能指针
std::unordered_map<std::string, std::shared_ptr<Session>> _sessions;
// 这里value也可以是Session类对象,但是使用智能指针可以自动管理内存,避免内存泄漏,
// 并且因为对于一个会话的历史消息越来越多,直接使用Session类对象会导致空间占用过高,而指针所占用的空间是固定的
// mutable关键字:允许在const成员函数中修改指定成员变量,确保const成员函数下也能线程安全
// 会话映射表的互斥锁:确保在多线程环境下对会话相关操作是安全访问的
mutable std::mutex _mutex;
// 记录会话的总数:atomic:确保在多线程环境下对会话总数的操作是线程安全的
std::atomic<int64_t> _sessionsCounter = {0};
};
} // namespace ai_chat_sdk
#endif
3.3 关键设计要点解析
3.3.1 存储结构:unordered_map + shared_ptr
std::unordered_map<std::string, std::shared_ptr<Session>> _sessions;
- 不直接在容器中存放
Session对象:Session 内部会持续追加大量消息,对象体积会不断变大,直接存储会带来高额拷贝开销;放到堆上由shared_ptr管理,指针本身占用空间固定,拷贝代价极低; - 以
sessionId字符串作为 key,哈希表查询平均复杂度 O (1),适合高频的会话查找场景; - 智能指针自动管理生命周期,当没有任何地方引用这个 Session 时,自动释放内存,规避内存泄漏。
3.3.2 mutable std::mutex _mutex
mutable关键字是这里的重点:
- 被
const修饰的成员函数,语义上承诺不会修改类的业务数据; - 但是
mutex的 lock/unlock 本身会修改互斥锁内部状态,普通 mutex 无法在 const 函数里面调用; mutable专门用来突破这个限制,标记该成员不受 const 语义约束;- 典型场景:
getSession、getSessionMessages这类 const 查询接口,依然需要加锁保护共享容器,防止其他线程并发修改。
3.3.3 std::atomic<int64_t> _sessionCounter
这里有一个很容易产生疑问的点:
既然在创建会话过程中我们会进行加锁保护,那么生成会话id也就是修改计数器的操作已经是在锁保护范围内,普通 int64_t 理论上也可以保证数据安全,为什么还要选择 atomic?
- 无锁读取(核心优势):
getSessionCount()是一个 const 查询接口,如果使用普通变量,读取时也必须加锁;而atomic支持无锁读取,高并发查询场景下可以减少锁竞争,提升查询性能。 - 防御性编程:如果后续代码迭代,不小心把计数器自增逻辑移出锁保护范围,普通变量会直接引发数据竞争、诡异 bug;
atomic无论是否在锁内,都能保证基础的原子语义,提升代码健壮性。 - 语义自解释:
atomic向阅读代码的开发者传递明确信息:这个变量是一个用于并发计数的变量,本身就具备并发访问的语义,可读性更强。
补充:锁内的原子自增相比普通变量自增,性能损耗很小,工程上优先选择 atomic,换取更好的并发查询能力与代码鲁棒性。


3.3.4 接口设计原则:全部基于 sessionId 操作
对外所有接口都只传递sessionId字符串,不会直接把Session裸指针或者 shared_ptr 随意暴露给外部模块:
- 降低模块耦合;
- 为后续持久化(SQLite 存储会话)预留扩展空间,数据库 CURD 天然使用字符串 ID 作为主键;
- 避免跨模块传递内存指针带来的生命周期风险。
3.3.5 私有辅助函数
generateSessionId():生成全局唯一会话编号;generateMessageId():为会话内每一条消息生成独立消息 ID;ID 生成逻辑封装在管理器内部,外部业务不需要关心 ID 生成规则。
四、源文件实现(SessionManager.cpp)
4.1 私有辅助函数:ID 生成函数
4.1.1 generateSessionId () 生成会话 ID
std::string SessionManager::generateSessionId()
{
// 会话总数自增
_sessionCounter.fetch_add(1);
// 获取当前时间戳
std::time_t time = std::time(nullptr);
std::ostringstream oss;
// 格式化输出:session_时间戳_8位序号,不足8位前置补0
oss << "session_" << time << "_" << std::setw(8) << std::setfill('0') << _sessionCounter;
return oss.str();
}
设计说明:
会话 ID 格式:session_<unix时间戳>_<8位十进制序号>
- unix 时间戳保证跨时间、大范围场景下的唯一性;
- 原子计数器
_sessionCounter解决同一秒内批量创建会话时 ID 冲突问题; std::setw(8)+std::setfill('0')固定宽度补零,ID 格式统一,方便检索展示。
4.1.2 generateMessageId (size_t messageCount) 生成消息 ID
std::string SessionManager::generateMessageId(size_t messageCount)
{
// 获取当前时间戳
std::time_t time = std::time(nullptr);
std::ostringstream oss;
// 格式化输出:msg_时间戳_当前会话内消息序号
oss << "msg_" << time << "_" << std::setw(8) << std::setfill('0') << messageCount;
return oss.str();
}
设计说明:
消息 ID 格式:msg_<unix时间戳>_<会话内消息序号>
- 入参
messageCount取自目标会话_messages数组当前 size,代表本条是该会话内第 N 条消息; - 唯一性约束是会话内唯一,不同会话允许出现相同序号的消息 ID,搭配 sessionId 一起使用就可以全局定位一条消息。
两个 ID 生成函数作为私有方法,外部完全不需要关心 ID 生成规则,由管理器内部统一控制。
4.2 createSession 创建会话
std::string SessionManager::createSession(const std::string &modelName)
{
// RAII锁,出作用域自动解锁
std::lock_guard<std::mutex> lock(&_mutex);
// 在堆上创建Session对象,shared_ptr托管生命周期
std::shared_ptr<Session> session = std::make_shared<Session>(modelName);
// 生成唯一sessionId
std::string sessionId = generateSessionId();
// 绑定会话ID
session->_session_id = sessionId;
// 创建时,创建时间、更新时间戳都初始化为当前时间
session->_created_at = session->_updated_at = std::time(nullptr);
// 存入哈希映射表
_sessions[sessionId] = session;
return sessionId;
}
逻辑拆解:
- 加锁:所有操作共享容器
_sessions的地方,都用lock_guard,RAII 机制,函数 return、异常抛出都会自动释放锁,杜绝忘记 unlock、死锁; - 构造 Session 智能指针对象,绑定本次会话使用的模型名称;
- 生成 sessionId 并赋值,初始化创建、更新时间;
- 把
sessionId -> shared_ptr<Session>存入 unordered_map; - 返回 sessionId 给上层业务,后续所有操作都拿着这个字符串 ID 访问会话。
4.3 getSession 获取会话智能指针
std::shared_ptr<Session> SessionManager::getSession(const std::string &sessionId) const
{
std::lock_guard<std::mutex> lock(&_mutex);
auto it = _sessions.find(sessionId);
if (it != _sessions.end())
{
return it->second;
}
return nullptr;
}
重点:
这是const成员函数,因为我们标记了_mutex为mutable,所以可以在 const 函数内上锁;
只做查询,找到返回会话 shared_ptr,找不到返回空指针。上层拿到 shared_ptr 之后,可以安全读取会话数据。
4.4 addMessage 向指定会话追加消息
bool SessionManager::addMessage(const std::string &sessionId, const Message &message)
{
std::lock_guard<std::mutex> lock(&_mutex);
auto it = _sessions.find(sessionId);
if (it == _sessions.end())
{
return false;
}
// 构造消息对象
Message msg(message._role, message._content);
// 基于当前会话已有消息数量,生成消息ID
std::string messageId = generateMessageId(it->second->_messages.size());
msg._message_id = messageId;
// 消息时间戳赋值
msg._timestamp = std::time(nullptr);
// 追加到会话消息列表
it->second->_messages.push_back(msg);
// 每次新增消息,自动刷新会话的最后更新时间
it->second->_updated_at = std::time(nullptr);
INFO("add message success, messageId: {}, messageContent: {}", messageId, msg._content);
return true;
}
业务流程说明:
这是支撑多轮对话的核心:
- 业务传入 sessionId 和消息(user 提问 /assistant 回答);
- 校验会话是否存在,不存在直接返回 false;
- 生成 messageId、填充消息时间戳;
- push_back 进入会话内部
_messages数组; - 自动更新
_updated_at,这一步很关键:后续会话列表排序 “最近会话置顶” 依靠这个字段; - 打印日志,方便调试,返回 true 代表消息保存成功。
业务侧使用习惯:用户发消息调用一次 addMessage 存 user 消息,拿到 LLM 返回结果之后,再调用一次 addMessage 保存 assistant 消息,上下文自动累积。
4.5 getSessionMessages 获取会话全部历史消息
std::vector<Message> SessionManager::getSessionMessages(const std::string &sessionId) const
{
std::lock_guard<std::mutex> lock(&_mutex);
auto it = _sessions.find(sessionId);
if (it == _sessions.end())
{
return {};
}
return it->second->_messages;
}
用途:
拿到有序的完整消息数组,直接传给LLMManager::sendMessage,作为对话上下文,不再需要业务手动维护消息列表。找不到会话返回空 vector。
4.6 getSessionLists 获取排序后的会话 ID 列表
std::vector<std::string> SessionManager::getSessionLists() const
{
std::lock_guard<std::mutex> lock(&_mutex);
std::vector<std::pair<std::string, std::time_t>> temp;
for (auto &pair : _sessions)
{
temp.emplace_back(pair.first, pair.second->_updated_at);
}
// 按照更新时间降序排序,最新会话排在前面
std::sort(temp.begin(), temp.end(), [](const auto &a, const auto &b)
{
return a.second > b.second;
});
// 提取排序后的sessionId
std::vector<std::string> sessionIds;
for (const auto &pair : temp)
{
sessionIds.push_back(pair.first);
}
return sessionIds;
}
排序逻辑讲解:
unordered_map本身是哈希表,遍历顺序是无序的,UI 展示历史对话要求最近交互的会话置顶:
- 先遍历
unordered_map,把sessionId + updated_at存入临时 vector; std::sort配合 lambda 比较器,按时间戳从大到小降序;- 从排好序的临时数组提取 sessionId 返回给上层 UI;上层拿到有序 sessionId 列表后,循环调用
getSession拿到会话元信息渲染界面。

4.7 updateSessionTimestamp 更新会话时间戳
void SessionManager::updateSessionTimestamp(const std::string &sessionId)
{
std::lock_guard<std::mutex> lock(&_mutex);
auto it = _sessions.find(sessionId);
if (it == _sessions.end())
{
return;
}
it->second->_updated_at = std::time(nullptr);
}
独立接口,适用于不需要新增消息、但是需要刷新会话更新时间的场景(例如:只查看会话,不发消息,也想让会话上浮);正常发消息的场景,addMessage内部已经自动更新时间戳,不需要额外调用。
4.8 deleteSession 删除指定会话
bool SessionManager::deleteSession(const std::string &sessionId)
{
std::lock_guard<std::mutex> lock(&_mutex);
auto it = _sessions.find(sessionId);
if (it == _sessions.end())
{
return false;
}
// 从哈希表擦除会话
_sessions.erase(it);
return true;
}
- 找到会话,执行 erase;
- 因为存储的是 shared_ptr,erase 之后,如果没有其他地方持有这个会话的 shared_ptr,Session 对象自动析构释放内存;
- 返回 true 代表删除成功,false 代表会话不存在。
4.9 deleteAllSessions 清空全部会话
void SessionManager::deleteAllSessions()
{
std::lock_guard<std::mutex> lock(&_mutex);
_sessions.clear();
_sessionCounter = 0;
}
清空整个哈希容器,同时重置原子计数器,一般用于重置 SDK、批量清理场景。
4.10 getSessionCount 获取当前会话总数
size_t SessionManager::getSessionCount() const
{
return _sessionCounter;
}
_sessionCounter 是 std::atomic 类型,支持无锁读取,高并发查询会话数量时性能更好,不需要加锁。
4.11 会话管理类完整源代码
#include "../include/SessionManager.h"
#include "../include/util/MyLog.h"
using namespace ply_Log;
namespace ai_chat_sdk
{
// 生成会话ID:会话id格式:session_时间戳_当前会话总数
std::string SessionManager::generateSessionId()
{
// 会话总数自增
_sessionsCounter.fetch_add(1);
// 获取当前时间戳
std::time_t time = std::time(nullptr);
// 生成会话ID
std::ostringstream oss;
// 格式化输出:session_12345678_00000001
oss << "session_" << time << "_" << std::setw(8) << std::setfill('0') << _sessionsCounter;
//std::setw(8):设置输出宽度为8个字符,不足的部分用'0'填充(std::setfill('0'))
return oss.str();
}
// 生成消息ID:消息id格式:msg_时间戳_当前会话中的消息总数
std::string SessionManager::generateMessageId(size_t messageCount)
{
// 消息总数自增
messageCount++;
// 获取当前时间戳
std::time_t time = std::time(nullptr);
// 生成消息ID
std::ostringstream oss;
// 格式化输出:msg_12345678_00000001
oss << "msg_" << time << "_" << std::setw(8) << std::setfill('0') << messageCount;
return oss.str();
}
// 创建会话:根据模型名称创建一个新的会话
std::string SessionManager::createSession(const std::string &modelName)
{
// RAII加锁保护,确保在多线程环境下对创建会话是线程安全的,并且在函数退出时自动释放锁
std::lock_guard<std::mutex> lock(_mutex);
// 创建会话对象指针
std::shared_ptr<Session> session = std::make_shared<Session>(modelName);
// 创建会话ID
std::string sessionId = generateSessionId();
// 将会话ID和会话对象指针进行绑定
session->_session_id = sessionId;
// 设置会话的创建时间戳和更新时间戳为当前时间
session->_created_at = session->_updated_at = std::time(nullptr);
// 将会话对象指针添加到会话映射表中
_sessions[sessionId] = session;
return sessionId;
}
// 获取会话:根据会话ID获取会话对象的智能指针
std::shared_ptr<Session> SessionManager::getSession(const std::string &sessionId) const
{
// 加锁保护,确保在多线程环境下对获取会话是线程安全的,并且在函数退出时自动释放锁
std::lock_guard<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
// 检查会话映射表中是否存在指定会话ID,如果存在则返回会话对象指针,否则返回空指针
if (it != _sessions.end())
{
return it->second;
}
return nullptr;
}
// 添加消息:将一条消息添加到指定会话的历史消息中
bool SessionManager::addMessage(const std::string &sessionId, const Message &message)
{
// 加锁保护,确保在多线程环境下对获取会话是线程安全的,并且在函数退出时自动释放锁
std::lock_guard<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
// 检查会话映射表中是否存在指定会话ID,如果不存在则返回false
if (it == _sessions.end())
{
return false;
}
// 创建消息
Message msg(message._role, message._content);
// 生成消息ID
std::string messageId = generateMessageId(it->second->_messages.size());
// 将消息ID添加到消息对象中
msg._message_id = messageId;
// 将消息对象添加到会话的历史消息中
it->second->_messages.push_back(msg);
// 添加信息:更新会话的更新时间戳为当前时间
it->second->_updated_at = std::time(nullptr);
INFO("add message success, messageId: {}, messageContent: {}", messageId, msg._content);
return true;
}
// 获取会话历史消息:根据会话ID获取指定会话的所有历史消息
std::vector<Message> SessionManager::getSessionMessages(const std::string &sessionId) const
{
// 加锁保护,确保在多线程环境下对获取会话是线程安全的,并且在函数退出时自动释放锁
std::lock_guard<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
// 检查会话映射表中是否存在指定会话ID,如果不存在则返回空向量
if (it == _sessions.end())
{
return {};
}
// 返回会话的所有历史消息
return it->second->_messages;
}
// 获取所有会话ID列表:返回所有当前存在的会话ID
std::vector<std::string> SessionManager::getSessionLists() const
{
// 加锁保护,确保在多线程环境下对获取会话列表是线程安全的,并且在函数退出时自动释放锁
std::lock_guard<std::mutex> lock(_mutex);
// 根据更新时间戳对会话ID进行降序排序,保证最新的会话ID在列表的最前面
std::vector<std::pair<std::string, std::time_t>> temp(_sessions.size());
for (auto pair : _sessions)
{
temp.emplace_back(pair.first, pair.second->_updated_at);
}
std::sort(temp.begin(), temp.end(), [](const auto &a, const auto &b)
{ return a.second > b.second; });
// 从排序后的会话ID-时间戳对中提取排完序的会话ID,构建会话ID列表
std::vector<std::string> sessionIds(temp.size());
for (const auto &pair : temp)
{
sessionIds.push_back(pair.first);
}
return sessionIds;
}
// 更新会话时间戳:根据会话ID更新指定会话的更新时间戳
void SessionManager::updateSessionTimestamp(const std::string &sessionId)
{
// 加锁保护,确保在多线程环境下对更新会话时间戳是线程安全的,并且在函数退出时自动释放锁
std::lock_guard<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
// 检查会话映射表中是否存在指定会话ID,如果不存在则返回
if (it == _sessions.end())
{
return;
}
// 更新会话的更新时间戳为当前时间
it->second->_updated_at = std::time(nullptr);
}
// 删除会话:根据会话ID删除指定会话
bool SessionManager::deleteSession(const std::string &sessionId)
{
// 加锁保护,确保在多线程环境下对删除会话是线程安全的,并且在函数退出时自动释放锁
std::lock_guard<std::mutex> lock(_mutex);
auto it = _sessions.find(sessionId);
// 检查会话映射表中是否存在指定会话ID,如果不存在则返回false
if (it == _sessions.end())
{
return false;
}
// 从会话映射表中删除指定会话ID
_sessions.erase(it);
// 修改会话总数,会话总数减1
_sessionsCounter.fetch_sub(1);
return true;
}
// 删除所有会话:删除所有当前存在的会话
void SessionManager::deleteAllSessions()
{
// 加锁保护,确保在多线程环境下对删除会话是线程安全的,并且在函数退出时自动释放锁
std::lock_guard<std::mutex> lock(_mutex);
// 清空会话映射表,删除所有会话
_sessions.clear();
// 重置会话总数为0
_sessionsCounter = 0;
}
// 获取会话总数:返回当前存在的会话总数
size_t SessionManager::getSessionsCount() const
{
// 无需加锁,因为_sessionsCounter是原子操作,所以不会出现线程安全问题
return _sessionsCounter;
}
} // namespace ai_chat_sdk
4.12 整体业务调用流程汇总
- 新建对话:
createSession(modelName)拿到 sessionId; - 用户提问:
addMessage(sessionId, userMsg)保存用户消息; - 取上下文:
getSessionMessages(sessionId)拿到完整消息 vector; - 调用大模型:把消息 vector 传给
LLMManager::sendMessage; - 模型返回结果:
addMessage(sessionId, assistantMsg)保存回答,自动刷新会话更新时间; - UI 加载历史会话:
getSessionLists()拿到有序会话 ID 列表,逐个getSession渲染; - 用户删除对话:
deleteSession(sessionId)。
4.13 线程安全方案总结
- 共享容器
_sessions全部读写都使用lock_guardRAII 锁,自动管理锁生命周期; mutable std::mutex解决 const 只读查询函数内部上锁的语法限制;- 会话计数器
_sessionCounter使用std::atomic,支持无锁读取,并发计数安全; - 锁只保护管理器内部的哈希表,会话本身的数据在 shared_ptr 托管下,生命周期安全。
五、SessionManager 单元测试
基于 GTest 框架编写SessionManager的功能冒烟测试,结合我们上面实现的会话管理器,验证会话创建、消息追加、会话排序、时间戳更新、删除等整套接口逻辑,同时展示主测试入口与运行输出结果。
5.1 测试用例代码
TEST(SessionManagerTest, operSession)
{
// 1、创建SessionManager对象
SessionManager sessions;
// 2、创建多个会话
std::string deepseek_id = sessions.createSession("deepseek-flash");
sleep(1);
std::string gemini_id = sessions.createSession("gemini-3.6-flash");
std::cout << deepseek_id << std::endl;
std::cout << gemini_id << std::endl;
std::cout << "sessionsCount: " << sessions.getSessionsCount() << std::endl;
sleep(1);
// 3、添加消息到会话
bool res;
res = sessions.addMessage(deepseek_id, {"user", "hello"});
ASSERT_TRUE(res);
res = sessions.addMessage(deepseek_id, {"user", "world"});
ASSERT_TRUE(res);
// 4、获取会话消息
std::cout << "-----------------获取指定会话及会话消息-----------------" << std::endl;
auto session_ptr = sessions.getSession(deepseek_id);
ASSERT_TRUE(session_ptr != nullptr);
std::cout << session_ptr->_model_name << ": " << session_ptr->_session_id << std::endl;
std::vector<Message> messages = session_ptr->_messages;
for (auto &message : messages)
{
std::cout << message._role << ": " << message._content << std::endl;
}
// 5、获取所有会话ID列表
std::cout << "-----------------获取所有会话ID列表-----------------" << std::endl;
std::vector<std::string> sessions_id = sessions.getSessionLists();
for (auto &session_id : sessions_id)
{
std::cout << session_id << std::endl;
}
// 6、更新会话时间戳
sleep(5);
std::cout << "-----------------更新指定会话更新时间戳-----------------" << std::endl;
sessions.updateSessionTimestamp(gemini_id);
std::cout << "deepseek: " << sessions.getSession(deepseek_id)->_updated_at << std::endl;
std::cout << "gemini: " << sessions.getSession(gemini_id)->_updated_at << std::endl;
// 7、更新会话时间戳后获取所有会话ID列表:会话ID列表按更新时间戳重新进行降序排序
std::cout << "-----------------更新会话时间戳后获取所有会话ID列表-----------------" << std::endl;
sessions_id = sessions.getSessionLists();
for (auto &session_id : sessions_id)
{
std::cout << session_id << std::endl;
}
// 8、删除所有会话
std::cout << "-----------------删除所有会话-----------------" << std::endl;
sessions.deleteAllSessions();
std::cout << "sessionsCount: " << sessions.getSessionsCount() << std::endl;
}
5.2 测试入口函数
int main(int argc, char **argv)
{
// 初始化Google Test框架
testing::InitGoogleTest(&argc, argv);
// 初始化日志系统
Logger::logger_init("testLLM", "stdout", spdlog::level::info);
// 运行所有测试用例
return RUN_ALL_TESTS();
}
这个测试属于集成冒烟测试,不是细分的单元测试,目的是串行跑通 SessionManager 整套业务链路:
- 覆盖 ID 生成、会话 CRUD、消息存储、时间戳、会话排序核心能力;
- 使用 GTest 断言捕获异常分支,控制台打印方便我们直观观察数据;
- sleep 用来人为制造时间差,专门用来测试基于
updated_at的排序逻辑; - 没有单独写多线程并发测试,后续可以额外补充并发读写的压力测试用例。
运行结果展示:

结束语
到这里,
SessionManager会话管理模块的设计、编码与测试就全部完成了。我们从业务痛点出发,一步步完成结构体定义、头文件接口设计、各个成员函数的落地实现,同时针对多线程场景做了锁、原子变量等线程安全保障,最后通过 GTest 测试用例跑通了整套会话生命周期流程,验证了会话创建、消息追加、时间戳更新、会话排序、会话删除等核心功能。会话管理器补齐了 SDK 里对话上下文管理的短板,它和前面的 LLMProvider、LLMManager 共同构成完整的调用链路:上层业务依靠 SessionManager 维护对话历史,再交由 LLMManager 调度对应的模型 Provider 完成请求。
更多推荐

所有评论(0)