基于C++实现内存缓存器的具体方案
本文介绍一个基于 C++11 标准库实现的轻量级内存缓存的工具库。该库覆盖了覆盖常规缓存操作,如set、get等。
1. 背景
某C++的服务程序对外提供幂等接口,除数据库外,没有依赖第三方中间件。近期进行功能迭代,需要使用缓存,常见的解决方案是引入 Redis 等外部缓存中间件。
但我不想使用。一方面,这个程序已经使用了json库、cgi库和oci库,部署比较麻烦,在我接手前,生产环境的若干台已经能正常运行了,我接手后,就持续要部署到不同的环境,鉴于个人的水平问题,没有能力将编码和运维的工作分开,为方便日后的自己,花了一些力气做了容器化改造。
因此,作为集多种不同工程于一身且兼顾研发和运维的我而言,额外的东西能不加就不加,引入额外依赖只是增加烦恼耳。
闲话不提,回到题目本身,本方即设计一款零外部依赖、开箱即用、线程安全的内存缓存器,来满足以下需求:
- 常规化设计,符合一般人对cache的认知。
- 可使用多种字段为 key 标记,缓存已处理的请求。
- 缓存项在可配置的时间(默认 10 分钟)后自动过期。
- 多线程环境下安全读写。
- 代码简洁,易于集成到现有 C++ 工程。
2. 设计
2.1 整体架构
┌─────────────────────────────────────────┐ │ SimpleCache │ │ (核心缓存器,通用 key-value 缓存) │ ├────────────┬────────────┬───────────────┤ │ unordered │ mutex │ 后台清理线程 │ │ _map │ (读写锁) │ (可选) │ └────────────┴────────────┴───────────────┘
SimpleCache作为通用的 key-value 内存缓存类,提供 set/get/exists/remove/cleanup等接口。
2.2 核心数据结构
struct CacheEntry {
std::string value;
std::chrono::steady_clock::time_point expireAt;
};
std::unordered_map<std::string, CacheEntry> m_cache;
std::mutex m_mutex;
unordered_map提供 O(1) 平均查找时间CacheEntry存储值和过期时间点mutex保证线程安全,所有公开接口使用lock_guard保护
2.3 过期策略:惰性 + 定期双机制
| 机制 | 触发时机 | 特点 |
|---|---|---|
| 惰性过期 | 每次 exists / get 查询时判断 | 即时清理,不占用额外线程 |
| 后台定期清理 | 独立线程按间隔运行 cleanup() | 防止长期未访问的过期条目堆积 |
构造函数中 cleanupInterval 参数控制后台清理行为:
explicit SimpleCache(int expireSeconds = 600, int cleanupInterval = 60);
cleanupInterval = 0:仅惰性过期,不创建后台线程cleanupInterval > 0:启动后台线程,按指定秒数间隔全量扫描清理
2.4 线程安全设计
- 所有公开方法首行获取
std::lock_guard<std::mutex>,函数退出时自动释放 - 后台清理线程与业务线程共享同一
mutex,互斥访问m_cache - 析构时通过
atomic<bool> m_running通知后台线程退出并join,确保安全释放
3. 实践
3.1 基本用法
#include "SimpleCache.h"
// 创建缓存器,10 分钟过期,60 秒后台清理
SimpleCache cache(600, 60);
// 写入
cache.set("key1", "value1");
// 查询是否存在
if (cache.exists("key1")) {
// 命中
}
// 取值
std::string val;
if (cache.get("key1", val)) {
std::cout << val << std::endl;
}
// 删除
cache.remove("key1");
// 清空
cache.clear();
3.3 集成到现有工程
仅需将 SimpleCache.h 和 SimpleCache.cpp 加入编译列表,无任何第三方库依赖。
4. 测试
4.1 编译
g++ -std=c++11 -pthread -o test_cache SimpleCache.cpp test_cache.cpp
编译选项说明:
-std=c++11:使用 C++11 标准(chrono、thread、atomic等)-pthread:链接 pthread 库(后台清理线程)
4.2 测试用例
| 类别 | 用例 | 验证点 |
|---|---|---|
| 基本功能 | set / exists | 写入后可查询,不存在 key 返回 false |
| 基本功能 | get 取值 | 正确返回写入的值 |
| 基本功能 | remove 删除 | 删除后不可查询,删除不存在 key 不崩溃 |
| 基本功能 | 覆盖写入 | 相同 key 多次写入取最新值 |
| 过期机制 | 惰性过期 | 1 秒 TTL,等待 1.1 秒后 exists 返回 false |
| 过期机制 | 手动清理 | cleanup() 正确清理全部过期条目并返回清理数 |
| 过期机制 | 自定义 TTL | 短 TTL 项过期、长 TTL 项未过期 |
| 过期机制 | 后台定期清理 | 1 秒过期 + 1 秒清理间隔,2.5 秒后 size 归零 |
| 缓存管理 | clear 清空 | 清空后 size 为 0 |
| 线程安全 | 并发读写 | 10 线程各写 100 条 + 10 线程各读 100 条,无崩溃无数据竞争 |
4.3 运行结果
$ ./test_cache ======================================== SimpleCache 单元测试 ======================================== [1] 基本功能 ---------------------------------------- [TEST] set / exists 基本功能 ... PASSED [TEST] get 取值 ... PASSED [TEST] remove 删除 ... PASSED [TEST] 覆盖写入 ... PASSED [TEST] clear 清空 ... PASSED [2] 过期机制 ---------------------------------------- [TEST] 惰性过期 ... PASSED [TEST] 手动清理过期条目 ... PASSED [TEST] 自定义 TTL ... PASSED [TEST] 后台线程定期清理 ... PASSED [3] 多字段组合 key ---------------------------------------- [TEST] 多字段组合 key(门架+车牌) ... PASSED [TEST] 多字段 key 过期 ... PASSED [TEST] 三字段组合 key ... PASSED [4] 多线程安全 ---------------------------------------- [TEST] 多线程安全 ... (hits=1000) PASSED ======================================== 结果: 13 通过, 0 失败 ========================================
5. 小结
本文设计并实现了一款基于 C++11 标准库的轻量级内存缓存器,具有以下特点:
优势:
- 零依赖:仅使用 C++ 标准库(
unordered_map、mutex、chrono、thread、atomic),无需 Redis 或任何第三方库 - 线程安全:
std::mutex+lock_guard保证并发场景下数据一致性 - 双过期机制:惰性删除保证查询即时性,后台线程防止内存堆积
- 接口简洁:
set/get/exists/remove/clear覆盖常规缓存操作
局限与改进方向:
- 当前不支持 LRU / LFU 等淘汰策略,在高容量场景下可考虑增加容量上限和淘汰逻辑
std::mutex在极高并发读场景下存在性能瓶颈,可升级为读写锁(std::shared_mutex,C++17)cleanup()全量遍历 O(n),条目数极大时可改为分段扫描- key 当前仅支持字符串类型,可模板化以支持泛型 key
适用场景:
嵌入式部署、边缘计算节点、以及在不想引入 Redis 等外部中间件的情况下需要一个小型本地缓存的 C++ 后台服务。
附录:源码
A.1 头文件 SimpleCache.h
#ifndef _SIMPLECACHE_H_
#define _SIMPLECACHE_H_
#include <string>
#include <unordered_map>
#include <mutex>
#include <chrono>
#include <thread>
#include <atomic>
#include <functional>
/**
* @brief 轻量级内存缓存器
*
* 线程安全,支持:
* - 添加 / 查询 / 删除
* - 可配置过期时间(默认 10 分钟)
* - 惰性过期 + 后台定期清理
* - 无外部依赖(纯 C++11 标准库)
*/
class SimpleCache
{
public:
/**
* @param expireSeconds 默认过期时间(秒),默认 600 秒 = 10 分钟
* @param cleanupInterval 后台清理间隔(秒),默认 60 秒;设为 0 则仅惰性过期
*/
explicit SimpleCache(int expireSeconds = 600, int cleanupInterval = 60);
~SimpleCache();
// 禁止拷贝
SimpleCache(const SimpleCache&) = delete;
SimpleCache& operator=(const SimpleCache&) = delete;
/**
* @brief 添加/更新缓存项
* @param key 缓存键(如:门架HEX + 车牌号)
* @param value 缓存值(可为空串,仅标记存在)
* @param ttl 该项的过期时间(秒),0 表示使用默认值
*/
void set(const std::string& key, const std::string& value = "", int ttl = 0);
/**
* @brief 查询缓存是否存在(未过期)
* @return true 存在且未过期, false 不存在或已过期
*/
bool exists(const std::string& key);
/**
* @brief 获取缓存值
* @param key 缓存键
* @param value [out] 缓存值
* @return true 命中, false 未命中或已过期
*/
bool get(const std::string& key, std::string& value);
/**
* @brief 删除指定缓存项
*/
void remove(const std::string& key);
/**
* @brief 清空全部缓存
*/
void clear();
/**
* @brief 获取当前缓存项数量(含已过期但尚未清理的)
*/
size_t size();
/**
* @brief 手动触发一次过期清理
* @return 清理掉的条目数
*/
size_t cleanup();
/**
* @brief 修改默认过期时间(秒)
*/
void setDefaultTTL(int seconds);
private:
struct CacheEntry
{
std::string value;
std::chrono::steady_clock::time_point expireAt;
};
std::unordered_map<std::string, CacheEntry> m_cache;
std::mutex m_mutex;
int m_defaultTTL; // 默认过期时间(秒)
int m_cleanupInterval; // 后台清理间隔(秒)
std::atomic<bool> m_running;
std::thread m_cleanupThread;
void cleanupLoop();
bool isExpired(const CacheEntry& entry);
};
#endif // _SIMPLECACHE_H_
A.2 实现文件SimpleCache.cpp
#include "SimpleCache.h"
#include <algorithm>
// ============================================================
// SimpleCache 实现
// ============================================================
SimpleCache::SimpleCache(int expireSeconds, int cleanupInterval)
: m_defaultTTL(expireSeconds)
, m_cleanupInterval(cleanupInterval)
, m_running(false)
{
if (m_cleanupInterval > 0)
{
m_running = true;
m_cleanupThread = std::thread(&SimpleCache::cleanupLoop, this);
}
}
SimpleCache::~SimpleCache()
{
m_running = false;
if (m_cleanupThread.joinable())
{
m_cleanupThread.join();
}
}
void SimpleCache::set(const std::string& key, const std::string& value, int ttl)
{
int effectiveTTL = (ttl > 0) ? ttl : m_defaultTTL;
CacheEntry entry;
entry.value = value;
entry.expireAt = std::chrono::steady_clock::now()
+ std::chrono::seconds(effectiveTTL);
std::lock_guard<std::mutex> lock(m_mutex);
m_cache[key] = std::move(entry);
}
bool SimpleCache::exists(const std::string& key)
{
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_cache.find(key);
if (it == m_cache.end())
{
return false;
}
if (isExpired(it->second))
{
m_cache.erase(it);
return false;
}
return true;
}
bool SimpleCache::get(const std::string& key, std::string& value)
{
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_cache.find(key);
if (it == m_cache.end())
{
return false;
}
if (isExpired(it->second))
{
m_cache.erase(it);
return false;
}
value = it->second.value;
return true;
}
void SimpleCache::remove(const std::string& key)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_cache.erase(key);
}
void SimpleCache::clear()
{
std::lock_guard<std::mutex> lock(m_mutex);
m_cache.clear();
}
size_t SimpleCache::size()
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_cache.size();
}
size_t SimpleCache::cleanup()
{
std::lock_guard<std::mutex> lock(m_mutex);
size_t removed = 0;
for (auto it = m_cache.begin(); it != m_cache.end(); )
{
if (isExpired(it->second))
{
it = m_cache.erase(it);
++removed;
}
else
{
++it;
}
}
return removed;
}
void SimpleCache::setDefaultTTL(int seconds)
{
m_defaultTTL = seconds;
}
void SimpleCache::cleanupLoop()
{
while (m_running)
{
// 以 1 秒粒度检查是否该执行清理
for (int i = 0; i < m_cleanupInterval && m_running; ++i)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (m_running)
{
cleanup();
}
}
}
bool SimpleCache::isExpired(const CacheEntry& entry)
{
return std::chrono::steady_clock::now() >= entry.expireAt;
}
A.3 测试文件test_cache.cpp
#include "SimpleCache.h"
#include <iostream>
#include <cassert>
#include <thread>
#include <vector>
#include <sstream>
// 简单测试宏
static int g_passed = 0;
static int g_failed = 0;
#define TEST(name) \
do { \
std::cout << " [TEST] " << name << " ... "; \
} while(0)
#define PASS() \
do { \
std::cout << "PASSED" << std::endl; \
++g_passed; \
} while(0)
#define CHECK(cond) \
do { \
if (!(cond)) { \
std::cout << "FAILED (" << __LINE__ << ")" << std::endl; \
++g_failed; \
return; \
} \
} while(0)
// ============================================================
// SimpleCache 单元测试
// ============================================================
void test_set_and_exists()
{
TEST("set / exists 基本功能");
SimpleCache cache(600, 0); // 惰性过期,无后台线程
cache.set("key1", "val1");
cache.set("key2");
CHECK(cache.exists("key1") == true);
CHECK(cache.exists("key2") == true);
CHECK(cache.exists("key3") == false);
PASS();
}
void test_get()
{
TEST("get 取值");
SimpleCache cache(600, 0);
cache.set("k", "hello world");
std::string v;
CHECK(cache.get("k", v) == true);
CHECK(v == "hello world");
// 不存在的 key
CHECK(cache.get("no_such_key", v) == false);
PASS();
}
void test_remove()
{
TEST("remove 删除");
SimpleCache cache(600, 0);
cache.set("k1");
cache.set("k2");
cache.remove("k1");
CHECK(cache.exists("k1") == false);
CHECK(cache.exists("k2") == true);
// 删除不存在的 key 不崩溃
cache.remove("k999");
PASS();
}
void test_overwrite()
{
TEST("覆盖写入");
SimpleCache cache(600, 0);
cache.set("k", "old");
cache.set("k", "new");
std::string v;
cache.get("k", v);
CHECK(v == "new");
PASS();
}
void test_expire_lazy()
{
TEST("惰性过期");
SimpleCache cache(1, 0); // 1 秒过期
cache.set("k");
CHECK(cache.exists("k") == true);
// 等待过期
std::this_thread::sleep_for(std::chrono::milliseconds(1100));
// 访问时触发惰性删除
CHECK(cache.exists("k") == false);
PASS();
}
void test_manual_cleanup()
{
TEST("手动清理过期条目");
SimpleCache cache(1, 0); // 1 秒过期
cache.set("a");
cache.set("b");
cache.set("c");
std::this_thread::sleep_for(std::chrono::milliseconds(1100));
size_t before = cache.size();
CHECK(before == 3); // 尚未清理,size 仍为 3
size_t removed = cache.cleanup();
CHECK(removed == 3);
CHECK(cache.size() == 0);
PASS();
}
void test_clear()
{
TEST("clear 清空");
SimpleCache cache(600, 0);
cache.set("a");
cache.set("b");
cache.set("c");
CHECK(cache.size() == 3);
cache.clear();
CHECK(cache.size() == 0);
PASS();
}
void test_custom_ttl()
{
TEST("自定义 TTL");
SimpleCache cache(600, 0);
cache.set("short", "", 1); // 1 秒过期
cache.set("long", "", 600); // 10 分钟过期
CHECK(cache.exists("short") == true);
CHECK(cache.exists("long") == true);
std::this_thread::sleep_for(std::chrono::milliseconds(1100));
CHECK(cache.exists("short") == false);
CHECK(cache.exists("long") == true);
PASS();
}
void test_thread_safety()
{
TEST("多线程安全");
SimpleCache cache(600, 0);
const int N = 1000;
std::vector<std::thread> threads;
// 多线程并发写入
for (int i = 0; i < 10; ++i)
{
threads.emplace_back([&cache, i, N]() {
for (int j = 0; j < N / 10; ++j)
{
std::ostringstream oss;
oss << "t" << i << "_" << j;
cache.set(oss.str(), "x");
}
});
}
for (auto& t : threads) t.join();
// 最多 N 条(可能有覆盖的)
CHECK(cache.size() <= (size_t)N);
// 多线程并发读取
threads.clear();
std::atomic<int> hits{0};
for (int i = 0; i < 10; ++i)
{
threads.emplace_back([&cache, &hits, N]() {
for (int j = 0; j < N / 10; ++j)
{
std::ostringstream oss;
oss << "t0_" << j;
if (cache.exists(oss.str()))
++hits;
}
});
}
for (auto& t : threads) t.join();
std::cout << "(hits=" << hits << ") ";
PASS();
}
void test_background_cleanup()
{
TEST("后台线程定期清理");
SimpleCache cache(1, 1); // 1 秒过期 + 1 秒清理间隔
cache.set("bg1");
cache.set("bg2");
// 等待后台线程清理
std::this_thread::sleep_for(std::chrono::milliseconds(2500));
// 后台清理后,size 应为 0
CHECK(cache.size() == 0);
PASS();
}
// ============================================================
// 多字段组合 key 场景测试
// ============================================================
// 工具函数:将多个字段组装为缓存 key,分隔符 '|'
static std::string makeKey(const std::string& a, const std::string& b)
{
return a + "|" + b;
}
static std::string makeKey(const std::string& a, const std::string& b,
const std::string& c)
{
return a + "|" + b + "|" + c;
}
void test_multi_field_key()
{
TEST("多字段组合 key(门架+车牌)");
SimpleCache cache(600, 0);
// 标记车辆通过门架
cache.set(makeKey("01G001", "京A12345"));
cache.set(makeKey("01G001", "京B67890"));
cache.set(makeKey("02G002", "京A12345"));
// 查询命中
CHECK(cache.exists(makeKey("01G001", "京A12345")) == true);
CHECK(cache.exists(makeKey("01G001", "京B67890")) == true);
CHECK(cache.exists(makeKey("02G002", "京A12345")) == true);
// 查询未命中
CHECK(cache.exists(makeKey("01G001", "京C00000")) == false);
CHECK(cache.exists(makeKey("03G003", "京A12345")) == false);
// 删除
cache.remove(makeKey("01G001", "京A12345"));
CHECK(cache.exists(makeKey("01G001", "京A12345")) == false);
CHECK(cache.exists(makeKey("01G001", "京B67890")) == true);
CHECK(cache.size() == 2);
PASS();
}
void test_multi_field_key_expire()
{
TEST("多字段 key 过期");
SimpleCache cache(1, 0);
cache.set(makeKey("01G001", "京A12345"));
CHECK(cache.exists(makeKey("01G001", "京A12345")) == true);
std::this_thread::sleep_for(std::chrono::milliseconds(1100));
CHECK(cache.exists(makeKey("01G001", "京A12345")) == false);
PASS();
}
void test_three_field_key()
{
TEST("三字段组合 key");
SimpleCache cache(600, 0);
std::string k1 = makeKey("01G001", "京A12345", "etc");
std::string k2 = makeKey("01G001", "京A12345", "cpc");
std::string k3 = makeKey("02G002", "京A12345", "etc");
cache.set(k1);
cache.set(k2);
cache.set(k3);
CHECK(cache.exists(k1) == true);
CHECK(cache.exists(k2) == true);
CHECK(cache.exists(k3) == true);
// 不同字段组合不会碰撞
CHECK(cache.exists(makeKey("01G001", "京A12345")) == false);
CHECK(cache.size() == 3);
PASS();
}
// ============================================================
// 主函数
// ============================================================
int main()
{
std::cout << "========================================" << std::endl;
std::cout << " SimpleCache 单元测试" << std::endl;
std::cout << "========================================" << std::endl;
std::cout << std::endl << "[1] 基本功能" << std::endl;
std::cout << "----------------------------------------" << std::endl;
test_set_and_exists();
test_get();
test_remove();
test_overwrite();
test_clear();
std::cout << std::endl << "[2] 过期机制" << std::endl;
std::cout << "----------------------------------------" << std::endl;
test_expire_lazy();
test_manual_cleanup();
test_custom_ttl();
test_background_cleanup();
std::cout << std::endl << "[3] 多字段组合 key" << std::endl;
std::cout << "----------------------------------------" << std::endl;
test_multi_field_key();
test_multi_field_key_expire();
test_three_field_key();
std::cout << std::endl << "[4] 多线程安全" << std::endl;
std::cout << "----------------------------------------" << std::endl;
test_thread_safety();
std::cout << std::endl << "========================================" << std::endl;
std::cout << " 结果: " << g_passed << " 通过, "
<< g_failed << " 失败" << std::endl;
std::cout << "========================================" << std::endl;
return (g_failed == 0) ? 0 : 1;
}
以上就是基于C++实现内存缓存器的具体方案的详细内容,更多关于C++内存缓存器实现方案的资料请关注脚本之家其它相关文章!
相关文章
Visual Studio 2022最新版安装教程(图文详解)
本文主要介绍了Visual Studio 2022最新版安装教程,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下2022-01-01


最新评论