头像


🎬 个人主页艾莉丝努力练剑

专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录
Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平

🎬 艾莉丝的简介:

在这里插入图片描述



在这里插入图片描述


1 ~> C++大模型SDK会话管理模块 SessionManager

核心业务背景:大语言模型本身不会保存会话上下文,所有多轮对话的会话、消息、时间状态必须业务侧手动维护;多线程并发访问会话管理器,必须做线程安全控制。

1.1 会话基础概念

1.1.1 会话定义

  • 会话:用户与大模型之间一组连续交互集合,维护聊天上下文与会话状态,保障多轮对话语义连贯。
  • 核心存储模型:[session_id → Session会话对象]映射关系,实现通过会话 ID 快速定位会话。

1.1.2 会话管理完整业务需求

  1. 创建会话:新建对话会话,分配唯一 session_id,绑定对话使用的模型名称。
  2. 通过 session_id 查询指定会话。
  3. 向指定会话追加用户 / 模型消息。
  4. 获取指定会话全部历史消息,用于组装给大模型的上下文请求。
  5. 获取全部会话 ID 列表,UI 展示历史会话。
  6. 删除指定会话。
  7. 更新会话最后更新时间戳。
  8. 清空全部会话。
  9. 获取当前会话总数量。
  10. 生成全局唯一会话 ID session_id
  11. 生成全局唯一消息 ID message_id
  12. 线程安全:多线程并发增删改查会话,互斥锁保护共享资源。

1.2 公共数据结构 common.h

1.2.1 Message 消息结构体

单条聊天消息,存储角色、消息内容、消息唯一 ID。

// common.h
#pragma once
#include <string>

struct Message {
    std::string _messageId;     // 消息唯一ID
    std::string _role;          // 角色:user / assistant / system
    std::string _content;        // 消息文本内容

    Message(const std::string& role, const std::string& content)
        : _role(role), _content(content) {}
};

1.2.2 Session 会话结构体

一个会话对应多组 Message,维护会话元信息与消息列表。

// common.h
#include <vector>
#include <ctime>

struct Session {
    std::string _sessionId;             // 会话唯一ID
    std::string _modelName;             // 当前会话使用的大模型名称
    std::vector<Message> _messages;     // 会话内全部消息集合
    std::time_t _createdAt;             // 会话创建时间戳
    std::time_t _updatedAt;             // 会话最后更新时间戳

    explicit Session(const std::string& modelName = "")
        : _modelName(modelName), _createdAt(std::time(nullptr)), _updatedAt(std::time(nullptr)) {}
};

1.3 SessionManager 头文件 SessionManager.h

1.3.1 头文件依赖说明

  • <unordered_map>:会话哈希存储,O (1) 平均复杂度查询;相比 map 红黑树,查询性能更高,会话 ID 有序性无业务要求。
  • <mutex>:互斥锁,多线程保护共享会话资源。
  • <memory>std::shared_ptr智能指针,管理堆上 Session 对象生命周期,规避裸指针风险。
  • <atomic>:原子变量,无锁计数,用于生成会话 ID 序号。
  • <ctime>:时间戳接口。
  • <sstream>:字符串流,拼接格式化 ID 字符串。
  • <algorithm>:sort 排序,会话列表按更新时间降序。

1.3.2 完整头文件代码

// sdk/include/SessionManager.h
#pragma once
#include <unordered_map>
#include <mutex>
#include <memory>
#include <atomic>
#include <vector>
#include <string>
#include <ctime>
#include <sstream>
#include <algorithm>
#include "common.h"

namespace ai_chat_sdk {

class SessionManager {
public:
    // 创建会话,传入模型名称,返回生成的session_id
    std::string createSession(const std::string& modelName);

    // 根据session_id获取会话,返回shared_ptr,不存在返回nullptr;const成员函数
    std::shared_ptr<Session> getSession(const std::string& sessionId) const;

    // 向指定会话追加消息;成功返回true,会话不存在返回false
    bool addMessage(const std::string& sessionId, const Message& message);

    // 获取指定会话全部历史消息;会话不存在返回空vector;const
    std::vector<Message> getHistoryMessages(const std::string& sessionId) const;

    // 更新会话最后更新时间戳
    void updateSessionTimestamp(const std::string& sessionId);

    // 获取全部会话ID列表,返回的列表按照会话_updatedAt时间戳降序排列;const
    std::vector<std::string> getSessionLists() const;

    // 删除指定session_id会话;存在删除返回true,不存在返回false
    bool deleteSession(const std::string& sessionId);

    // 清空内存中全部会话
    void clearAllSessions();

    // 获取当前会话总数量;const
    size_t getSessionCount() const;

private:
    // 私有工具:生成会话唯一ID,格式 session_时间戳_8位序号
    std::string generateSessionId();

    // 私有工具:生成消息唯一ID,格式 msg_时间戳_8位序号
    std::string generateMessageId(size_t messageCounter);

private:
    /**
     * 会话存储容器
     * key: session_id字符串
     * value: Session会话对象的shared_ptr,对象分配在堆内存
     * 使用unordered_map哈希表,平均O(1)查询效率
     */
    std::unordered_map<std::string, std::shared_ptr<Session>> _sessions;

    /**
     * mutable修饰互斥锁
     * const成员函数不能修改普通成员变量;
     * 但const方法依然需要加锁解锁修改mutex内部状态,因此使用mutable放开修改权限
     */
    mutable std::mutex _mutex;

    // 原子会话计数器;线程安全自增;用于拼接会话ID序号,初始值0
    std::atomic<int64_t> _sessionCounter{0};
};

} // end namespace ai_chat_sdk

1.3.3 关键设计要点

  1. 存储选择unordered_map<std::string, std::shared_ptr<Session>>
    1. 不直接存储 Session 对象值:避免 map 容器存储大量大对象带来的拷贝开销;会话对象放到堆内存,智能指针管理生命周期。
    2. key 会话 ID 字符串,平均 O (1) 查找。
  2. mutable std::mutex _mutex
    1. const 修饰成员函数语义:承诺不修改对象业务数据;
    2. 但是锁的 lock/unlock 会修改 mutex 对象内部状态,普通 mutex 无法在 const 函数使用;mutable专门用于这种场景。
  3. std::atomic<int64_t> _sessionCounter
    1. 原子变量,无锁线程安全自增,多线程并发创建会话不会出现序号重复。
  4. 对外接口全部使用 session_id 操作会话,不暴露内部 Session 对象给外部模块:
    1. 降低模块耦合;适配持久化 SQLite 数据库 CURD;避免跨进程传递裸内存指针。

1.4 SessionManager 源文件 SessionManager.cpp

// sdk/src/SessionManager.cpp
#include "SessionManager.h"
#include <iomanip>      // std::setw std::setfill

namespace ai_chat_sdk {

// 创建会话
std::string SessionManager::createSession(const std::string& modelName)
{
    // lock_guard RAII自动加锁解锁,离开作用域自动释放互斥锁
    std::lock_guard<std::mutex> lock(_mutex);

    // 生成唯一会话ID
    std::string sessionId = generateSessionId();

    // 在堆上构造Session对象,智能指针接管生命周期
    auto session = std::make_shared<Session>(modelName);
    session->_sessionId = sessionId;

    // 存入哈希map
    _sessions[sessionId] = session;

    return sessionId;
}

// 通过session_id获取会话
std::shared_ptr<Session> SessionManager::getSession(const std::string& sessionId) const
{
    std::lock_guard<std::mutex> lock(_mutex);

    auto iter = _sessions.find(sessionId);
    if (iter == _sessions.end()) {
        return nullptr;
    }
    return iter->second;
}

// 向会话追加消息
bool SessionManager::addMessage(const std::string& sessionId, const Message& message)
{
    std::lock_guard<std::mutex> lock(_mutex);

    auto iter = _sessions.find(sessionId);
    if (iter == _sessions.end()) {
        return false;
    }

    // 当前会话消息数量作为消息序号入参
    size_t msgSeq = iter->second->_messages.size();
    Message newMsg(message._role, message._content);
    newMsg._messageId = generateMessageId(msgSeq);

    // 消息追加进会话消息列表
    iter->second->_messages.push_back(newMsg);

    // 添加消息后更新会话最后修改时间戳
    iter->second->_updatedAt = std::time(nullptr);

    return true;
}

// 获取会话全部历史消息
std::vector<Message> SessionManager::getHistoryMessages(const std::string& sessionId) const
{
    std::lock_guard<std::mutex> lock(_mutex);

    auto iter = _sessions.find(sessionId);
    if (iter == _sessions.end()) {
        return {};
    }
    return iter->second->_messages;
}

// 更新会话时间戳
void SessionManager::updateSessionTimestamp(const std::string& sessionId)
{
    std::lock_guard<std::mutex> lock(_mutex);

    auto iter = _sessions.find(sessionId);
    if (iter == _sessions.end()) {
        return;
    }
    iter->second->_updatedAt = std::time(nullptr);
}

// 获取会话ID列表,按updatedAt降序(最新会话排在最前面)
std::vector<std::string> SessionManager::getSessionLists() const
{
    std::lock_guard<std::mutex> lock(_mutex);

    // 临时vector存储 pair<更新时间戳,会话智能指针>
    std::vector<std::pair<std::time_t, std::shared_ptr<Session>>> tempList;
    tempList.reserve(_sessions.size());

    for (const auto& item : _sessions) {
        tempList.emplace_back(item.second->_updatedAt, item.second);
    }

    // lambda比较器:时间戳降序,大的时间在前
    std::sort(tempList.begin(), tempList.end(),
        [](const std::pair<std::time_t, std::shared_ptr<Session>>& a,
           const std::pair<std::time_t, std::shared_ptr<Session>>& b)
        {
            return a.first > b.first;
        });

    // 提取排序完成后的session_id
    std::vector<std::string> resultIds;
    resultIds.reserve(tempList.size());
    for (const auto& item : tempList) {
        resultIds.push_back(item.second->_sessionId);
    }
    return resultIds;
}

// 删除指定会话
bool SessionManager::deleteSession(const std::string& sessionId)
{
    std::lock_guard<std::mutex> lock(_mutex);

    auto iter = _sessions.find(sessionId);
    if (iter == _sessions.end()) {
        return false;
    }
    _sessions.erase(iter);
    return true;
}

// 清空全部会话
void SessionManager::clearAllSessions()
{
    std::lock_guard<std::mutex> lock(_mutex);
    _sessions.clear();
}

// 获取会话总数
size_t SessionManager::getSessionCount() const
{
    std::lock_guard<std::mutex> lock(_mutex);
    return _sessions.size();
}

// 生成会话ID 格式: session_时间戳_8位0填充序号
std::string SessionManager::generateSessionId()
{
    // 原子变量fetch_add(1)线程安全自增
    int64_t seq = _sessionCounter.fetch_add(1, std::memory_order_relaxed);
    std::time_t now = std::time(nullptr);

    std::ostringstream oss;
    oss << "session_"
        << now
        << "_"
        << std::setw(8)
        << std::setfill('0')
        << seq;
    return oss.str();
}

// 生成消息ID 格式: msg_时间戳_8位0填充序号
std::string SessionManager::generateMessageId(size_t messageCounter)
{
    std::time_t now = std::time(nullptr);
    std::ostringstream oss;
    oss << "msg_"
        << now
        << "_"
        << std::setw(8)
        << std::setfill('0')
        << messageCounter;
    return oss.str();
}

} // end namespace ai_chat_sdk

1.5 核心技术细节拆解

1.5.1 线程安全实现

  • 全部读写_sessions共享容器的成员函数均使用std::lock_guard<std::mutex>RAII 锁。
  • RAII 特性:作用域结束自动析构释放锁,杜绝异常路径死锁、忘记 unlock。
  • mutable std::mutex _mutex解决 const 成员函数加锁问题。
  • 会话计数器_sessionCounter使用std::atomic,无锁自增,生成 ID 阶段不竞争互斥锁。

1.5.2 ID 生成规则

  1. session_id:session_<unix时间戳>_<8位十进制序号>
    1. unix 时间戳保证大范围时间维度唯一性;原子计数器保证同一秒内多个会话 ID 不重复;setw(8)+setfill('0')固定宽度补零格式化。
  2. message_id:msg_<unix时间戳>_<会话内消息序号>
    1. 序号取自会话内部 messages vector 当前 size,代表该会话内第 N 条消息。

1.5.3 getSessionLists 会话排序业务

  • 原始 unordered_map 容器本身无序,直接遍历返回 session_id 顺序不可控。
  • 业务需求 UI 展示历史会话,最近更新会话置顶,因此需要:
    • 遍历 map,将<updatedAt时间戳,会话shared_ptr>存入临时 vector;
    • std::sort配合 lambda 比较器,按_updatedAt降序;
    • 从排好序的临时容器提取 session_id 返回上层。

为什么不直接返回 Session 对象集合:

  1. 返回完整 Session 对象会触发大量对象拷贝,内存开销高;
  2. 上层模块统一使用 session_id 作为操作句柄,直接返回 Session 会提升模块耦合;
  3. 如果后续会话持久化 SQLite,全部数据库 CURD 都以 session_id 为主键,返回 ID 接口天然适配持久层。

1.5.4 内存模型:为什么使用shared_ptr<Session>

  • 如果unordered_map直接存储Session值对象,插入 map 会触发 Session 完整拷贝;会话消息 vector 体积大,拷贝开销巨大。
  • 使用std::make_shared在堆分配 Session 对象,map 只保存智能指针,拷贝仅复制指针,开销极小。
  • shared_ptr自动管理对象生命周期,erase 会话的时候引用计数自动递减,会话对象自动释放内存,杜绝内存泄漏。

1.6 常见面试考点

1.6.1 unordered_map vs map,会话存储选型

容器底层结构查询平均复杂度有序性
std::unordered_map哈希表O(1)无序,key 不排序
std::map红黑树O(logN)key 有序

**会话业务:**不需要 session_id 有序;优先选择 unordered_map 获得更高查询性能。

1.6.2 mutable 关键字使用场景

  • mutable 作用:突破 const 成员函数对成员变量的修改限制
  • 仅用于这种场景:const 承诺业务数据不变,但是内部同步原语(mutex)需要修改内部状态;业务数据绝对不可以标记 mutable。

1.6.3 std::atomic fetch_add

  • fetch_add(1, memory_order_relaxed)原子自增,多线程并发调用不会出现数据竞争。
  • 不要直接++_sessionCounter,虽然 atomic 支持重载 ++ 运算符,fetch_add语义更加明确。

1.6.4 RAII 锁 std::lock_guard

  • 不要手动调用_mutex.lock(); _mutex.unlock();,函数提前 return、抛出异常会导致锁忘记释放,产生死锁。
  • lock_guard 在对象构造加锁,析构释放锁,无论函数以任何路径退出都会释放互斥锁。

1.6.5 会话管理器的扩展方向(面试拓展)

  1. 内存会话数据持久化到 SQLite 磁盘,程序重启会话数据不丢失;
  2. 会话消息数量上限,自动裁剪旧消息,控制上下文窗口长度,适配大模型上下文 token 上限;
  3. 会话过期淘汰机制,长时间未访问会话做 LRU 淘汰;
  4. 读写锁优化:读多写少场景,替换 mutex 为std::shared_mutex,读共享写排他,提升并发读性能。

1.7 业务易错坑点总结

  1. const 成员函数中普通 mutex 无法 lock,忘记添加 mutable 修饰,编译报错。
  2. ID 格式化忘记引入<iomanip>头文件,std::setw / std::setfill编译报错。
  3. 排序 lambda 写反比较逻辑,会话列表变成旧会话置顶。
  4. 生成消息 ID 直接使用全局计数器,没有使用会话内消息序号,业务逻辑错误。
  5. 直接返回裸指针Session*给上层调用,存在悬空指针内存安全风险,必须使用std::shared_ptr
  6. addMessage 追加消息之后**忘记更新****_updatedAt**时间戳,会话排序功能失效。
  7. 多线程访问会话容器,忘记加锁,引发数据竞争、内存崩溃。

结尾

uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

艾莉丝努力练剑

C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主


👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。
❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。
【收藏】 把核心知识点存好,在需要时随时查、随时用。
💬 【评论】 分享你的经验或疑问,评论区一起交流避坑!

不要忘记给博主“一键四连”哦!

“今日练剑达成!”

“技术之路难免有困惑,但同行的人会让前进更有方向。”

结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主“一键四连”哦!

往期回顾

【AI大模型接入SDK】LLM会话管理模块设计

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡
૮₍ ˶ ˊ ᴥ ˋ˶₎ა

在这里插入图片描述

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐