实现线程安全的缓存¶
来源:https://github.com/byrnexu/betterquant2
/*!
* \file LRUCache.cpp
* \project BetterQuant
*
* \author byrnexu
* \date 2023/02/09
*
* \brief
*/
#pragma once
#include <cassert>
#include <cstddef>
#include <list>
#include <sstream>
#include <stdexcept>
#include <unordered_map>
namespace std::ext {
template <typename key_t, typename value_t>
class lru_cache;
template <typename key_t, typename value_t>
using lru_cache_sptr = std::shared_ptr<lru_cache<key_t, value_t>>;
template <typename key_t, typename value_t>
class lru_cache {
public:
typedef typename std::pair<key_t, value_t> key_value_pair_t;
typedef typename std::list<key_value_pair_t>::iterator list_iterator_t;
explicit lru_cache(std::size_t capacity) : capacity_(capacity) {}
void push(const key_t& key, const value_t& value,
bool key_must_be_unique = true) {
assert(cache_items_map_.size() == cache_items_list_.size());
cache_items_list_.push_front(key_value_pair_t(key, value));
if (key_must_be_unique) {
auto it = cache_items_map_.find(key);
if (it != cache_items_map_.end()) {
cache_items_list_.erase(it->second);
cache_items_map_.erase(it);
}
}
cache_items_map_.emplace(key, cache_items_list_.begin());
if (cache_items_map_.size() > capacity_) {
auto last = cache_items_list_.end();
last--;
cache_items_map_.erase(last->first);
cache_items_list_.pop_back();
}
}
std::pair<bool, value_t> pop(const key_t& key) {
assert(cache_items_map_.size() == cache_items_list_.size());
value_t value{};
const auto it = cache_items_map_.find(key);
if (it == cache_items_map_.end()) {
return std::make_pair(false, value);
} else {
value = it->second->second;
cache_items_list_.erase(it->second);
cache_items_map_.erase(it);
return std::make_pair(true, value);
}
return std::make_pair(false, value);
}
std::pair<bool, value_t> get(const key_t& key) {
assert(cache_items_map_.size() == cache_items_list_.size());
value_t value{};
const auto it = cache_items_map_.find(key);
if (it == cache_items_map_.end()) {
return std::make_pair(false, value);
} else {
cache_items_list_.splice(cache_items_list_.begin(), cache_items_list_,
it->second);
return std::make_pair(true, it->second->second);
}
return std::make_pair(false, value);
}
inline bool exists(const key_t& key) const {
assert(cache_items_map_.size() == cache_items_list_.size());
return cache_items_map_.find(key) != cache_items_map_.end();
}
inline std::string to_string() const {
std::ostringstream oss;
for (const auto& rec : cache_items_list_) {
oss << rec.first << "=" << rec.second << ",";
}
std::string ret = oss.str();
if (ret.empty() == false) ret.pop_back();
return ret;
}
inline void clear() {
assert(cache_items_map_.size() == cache_items_list_.size());
cache_items_list_.clear();
cache_items_map_.clear();
}
inline std::size_t size() const { return cache_items_map_.size(); }
inline std::size_t capacity() const noexcept { return capacity_; }
private:
const std::size_t capacity_;
std::list<key_value_pair_t> cache_items_list_;
std::unordered_multimap<key_t, list_iterator_t> cache_items_map_;
};
} // namespace std::ext
#pragma once
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <thread>
#include <type_traits>
// 在x86/x64架构下,用于在自旋等待中降低CPU功耗并提示超线程
#if defined(__i386__) || defined(__x86_64__)
#include <immintrin.h>
#define LFC_CPU_RELAX() _mm_pause()
#else
#define LFC_CPU_RELAX() std::this_thread::yield()
#endif
namespace lfc {
/**
* @brief 编译时FNV-1a哈希函数
*/
constexpr uint64_t fnv1a_hash(const char* str, size_t len) noexcept {
uint64_t hash = 14695981039346656037ull;
for (size_t i = 0; i < len; ++i) {
hash ^= static_cast<uint64_t>(str[i]);
hash *= 1099511628211ull;
}
return hash;
}
/**
* @brief 默认的哈希器,适用于任何可平凡复制的类型
*/
template <typename Key>
struct DefaultHasher {
uint64_t operator()(const Key& key) const noexcept {
static_assert(std::is_trivially_copyable_v<Key>,
"DefaultHasher requires a trivially copyable key.");
return fnv1a_hash(reinterpret_cast<const char*>(&key), sizeof(Key));
}
};
/**
* @brief 一个基于线性探测和标签位域的、线程安全的无锁哈希表(缓存)
* @tparam Key 键的类型。必须是可平凡复制的 (trivially copyable)。
* @tparam Value 值的类型。必须是可平凡复制的 (trivially copyable)。
* @tparam TableSize 哈希表的大小。必须是2的幂。
* @tparam Hasher 用于计算键哈希值的函数对象。
*/
template <typename Key, typename Value, size_t TableSize = 4096,
typename Hasher = DefaultHasher<Key> >
class LockFreeCache {
static_assert((TableSize > 0) && ((TableSize & (TableSize - 1)) == 0),
"TableSize must be a power of 2");
static_assert(std::is_trivially_copyable_v<Key> &&
std::is_trivially_copyable_v<Value>,
"Key and Value must be trivially copyable for this lock-free "
"implementation.");
private:
// Tag位域定义: | 63: valid | 62: writing | 61..0: hash |
static constexpr uint64_t VALID_BIT = 1ULL << 63;
static constexpr uint64_t WRITING_BIT = 1ULL << 62;
static constexpr uint64_t HASH_MASK = WRITING_BIT - 1;
struct alignas(64) Entry {
std::atomic<uint64_t> tag;
Key key;
Value value;
};
std::array<Entry, TableSize> table;
Hasher hasher;
static size_t probe_next(size_t index) {
return (index + 1) & (TableSize - 1);
}
public:
LockFreeCache() {
for (auto& entry : table) {
entry.tag.store(0, std::memory_order_relaxed);
}
}
bool update(const Key& key, const Value& value) {
uint64_t hash_val = hasher(key);
size_t index = hash_val & (TableSize - 1);
uint64_t partial_hash = hash_val & HASH_MASK;
for (size_t i = 0; i < TableSize; ++i) {
Entry* entry = &table[index];
uint64_t current_tag = entry->tag.load(std::memory_order_relaxed);
while (true) {
if (current_tag == 0) { // Case 1: 空槽位
uint64_t new_tag = WRITING_BIT | partial_hash;
if (entry->tag.compare_exchange_strong(current_tag, new_tag,
std::memory_order_acq_rel)) {
entry->key = key;
entry->value = value;
entry->tag.store(VALID_BIT | partial_hash,
std::memory_order_release);
return true;
}
continue;
}
if (current_tag & WRITING_BIT) { // Case 2: 正在写入
LFC_CPU_RELAX();
current_tag = entry->tag.load(std::memory_order_relaxed);
continue;
}
if ((current_tag & HASH_MASK) == partial_hash) { // Case 3: 有效槽位
std::atomic_thread_fence(std::memory_order_acquire);
if (entry->key == key) {
uint64_t expected_tag = VALID_BIT | partial_hash;
uint64_t new_tag = WRITING_BIT | partial_hash;
if (entry->tag.compare_exchange_strong(expected_tag, new_tag,
std::memory_order_acq_rel)) {
entry->value = value;
entry->tag.store(VALID_BIT | partial_hash,
std::memory_order_release);
return true;
}
current_tag = expected_tag;
continue;
}
}
break;
}
index = probe_next(index);
}
return false; // 表已满
}
std::optional<Value> get(const Key& key) const {
uint64_t hash_val = hasher(key);
size_t index = hash_val & (TableSize - 1);
uint64_t partial_hash = hash_val & HASH_MASK;
for (size_t i = 0; i < TableSize; ++i) {
const Entry* entry = &table[index];
uint64_t current_tag = entry->tag.load(std::memory_order_acquire);
while (true) {
if (current_tag == 0) {
return std::nullopt;
}
if (current_tag & WRITING_BIT) {
LFC_CPU_RELAX();
current_tag = entry->tag.load(std::memory_order_acquire);
continue;
}
if ((current_tag & HASH_MASK) == partial_hash) {
if (entry->key == key) {
return entry->value;
}
}
break;
}
index = probe_next(index);
}
return std::nullopt;
}
};
} // namespace lfc
#include <atomic>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <functional>
#include <iomanip>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include "lockfree_cache.hpp"
// --- 定义测试用的 Key 和 Value ---
struct SymbolKey {
char name[8]{};
SymbolKey() = default;
constexpr SymbolKey(const char* n) {
for (size_t i = 0; i < 7 && n[i] != '\0'; ++i) {
name[i] = n[i];
}
}
bool operator==(const SymbolKey& other) const {
return std::memcmp(name, other.name, 8) == 0;
}
};
namespace lfc {
template <>
struct DefaultHasher<SymbolKey> {
uint64_t operator()(const SymbolKey& key) const noexcept {
return fnv1a_hash(key.name, strnlen(key.name, 8));
}
};
} // namespace lfc
struct MarketData {
double price;
uint64_t volume;
bool operator==(const MarketData& other) const {
return price == other.price && volume == other.volume;
}
};
// 将缓存容量从 16384 增加到 65536,以容纳所有 40000 个键。
lfc::LockFreeCache<SymbolKey, MarketData, 65536> market_cache;
void writer_thread(int thread_id, int num_keys, std::atomic<bool>& start_flag) {
while (!start_flag.load()) {
} // 等待开始信号
for (int i = 0; i < num_keys; ++i) {
std::string key_str = "K" + std::to_string(thread_id * num_keys + i);
market_cache.update(SymbolKey(key_str.c_str()),
{(double)i, (uint64_t)thread_id});
}
}
void reader_thread(int num_writers, int num_keys_per_writer,
std::atomic<bool>& start_flag,
std::atomic<int>& found_count) {
while (!start_flag.load()) {
} // 等待开始信号
int found = 0;
for (int i = 0; i < num_writers * num_keys_per_writer; ++i) {
std::string key_str = "K" + std::to_string(i);
if (market_cache.get(SymbolKey(key_str.c_str()))) {
found++;
}
}
found_count += found;
}
int main() {
const int NUM_WRITERS = 4;
const int NUM_READERS = 4;
const int KEYS_PER_WRITER = 10000;
std::vector<std::thread> threads;
std::atomic<bool> start_flag(false);
std::atomic<int> total_found(0);
std::cout << "准备并发测试..." << std::endl;
std::cout << NUM_WRITERS << "个写入线程, " << NUM_READERS << "个读取线程。"
<< std::endl;
std::cout << "每个写入线程将写入 " << KEYS_PER_WRITER << " 个键。"
<< std::endl;
for (int i = 0; i < NUM_WRITERS; ++i) {
threads.emplace_back(writer_thread, i, KEYS_PER_WRITER,
std::ref(start_flag));
}
for (int i = 0; i < NUM_READERS; ++i) {
threads.emplace_back(reader_thread, NUM_WRITERS, KEYS_PER_WRITER,
std::ref(start_flag), std::ref(total_found));
}
std::cout << "3秒后开始..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
auto test_start_time = std::chrono::high_resolution_clock::now();
start_flag.store(true);
for (auto& t : threads) {
t.join();
}
auto test_end_time = std::chrono::high_resolution_clock::now();
// 主线程做最终验证
int final_check_found = 0;
const int total_keys = NUM_WRITERS * KEYS_PER_WRITER;
for (int i = 0; i < total_keys; ++i) {
std::string key_str = "K" + std::to_string(i);
if (market_cache.get(SymbolKey(key_str.c_str()))) {
final_check_found++;
}
}
std::chrono::duration<double, std::milli> test_duration =
test_end_time - test_start_time;
std::cout << "--- 测试结果 ---" << std::endl;
std::cout << "所有线程执行完毕,耗时: " << std::fixed << std::setprecision(2)
<< test_duration.count() << " ms" << std::endl;
std::cout << "读取线程在并发期间共找到 " << total_found.load() << " 个键。"
<< std::endl;
std::cout << "主线程最终检查,在缓存中找到 " << final_check_found << " / "
<< total_keys << " 个键。" << std::endl;
if (final_check_found == total_keys) {
std::cout << "测试成功: 所有写入的键都被正确找到。" << std::endl;
} else {
std::cout << "测试失败: " << total_keys - final_check_found
<< " 个键丢失或未被正确写入。" << std::endl;
}
return 0;
}
zhixuan@gpu04:~/cacheimpl$ ./cache_test
准备并发测试...
4个写入线程, 4个读取线程。
每个写入线程将写入 10000 个键。
3秒后开始...
--- 测试结果 ---
所有线程执行完毕,耗时: 5.29 ms
读取线程在并发期间共找到 120427 个键。
主线程最终检查,在缓存中找到 40000 / 40000 个键。
测试成功: 所有写入的键都被正确找到。
#pragma once
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <optional>
#include <thread>
#include <type_traits>
#if defined(__i386__) || defined(__x86_64__)
#include <immintrin.h>
#define LFC_CPU_RELAX() _mm_pause()
#else
#define LFC_CPU_RELAX() std::this_thread::yield()
#endif
namespace lfc {
constexpr uint64_t fnv1a_hash(const char* str, size_t len) noexcept {
uint64_t hash = 14695981039346656037ull;
for (size_t i = 0; i < len; ++i) {
hash ^= static_cast<uint64_t>(str[i]);
hash *= 1099511628211ull;
}
return hash;
}
template <typename Key>
struct DefaultHasher {
uint64_t operator()(const Key& key) const noexcept {
static_assert(std::is_trivially_copyable_v<Key>,
"DefaultHasher requires a trivially copyable key.");
return fnv1a_hash(reinterpret_cast<const char*>(&key), sizeof(Key));
}
};
template <typename Key, typename Value, size_t TableSize = 4096,
typename Hasher = DefaultHasher<Key> >
class LockFreeLruCache {
static_assert((TableSize > 0) && ((TableSize & (TableSize - 1)) == 0),
"TableSize must be a power of 2");
static_assert(std::is_trivially_copyable_v<Key> &&
std::is_trivially_copyable_v<Value>,
"Key and Value must be trivially copyable for this lock-free "
"implementation.");
private:
static constexpr uint64_t VALID_BIT = 1ULL << 63;
static constexpr uint64_t WRITING_BIT = 1ULL << 62;
static constexpr uint64_t HASH_MASK = WRITING_BIT - 1;
struct alignas(64) Entry {
std::atomic<uint64_t> tag;
Key key;
Value value;
std::atomic<uint64_t> last_access_time;
};
std::array<Entry, TableSize> table_;
Hasher hasher_;
std::atomic<uint64_t> access_timer_{0};
static size_t probe_next(size_t index) {
return (index + 1) & (TableSize - 1);
}
public:
LockFreeLruCache() {
for (auto& entry : table_) {
entry.tag.store(0, std::memory_order_relaxed);
entry.last_access_time.store(0, std::memory_order_relaxed);
}
}
bool put(const Key& key, const Value& value) {
uint64_t hash_val = hasher_(key);
size_t index = hash_val & (TableSize - 1);
uint64_t partial_hash = hash_val & HASH_MASK;
uint64_t new_time = access_timer_.fetch_add(1, std::memory_order_relaxed);
for (size_t i = 0; i < TableSize; ++i) {
Entry* entry = &table_[index];
uint64_t current_tag = entry->tag.load(std::memory_order_relaxed);
while (true) {
if (current_tag == 0) {
uint64_t new_tag_val = WRITING_BIT | partial_hash;
if (entry->tag.compare_exchange_strong(current_tag, new_tag_val,
std::memory_order_acq_rel)) {
entry->key = key;
entry->value = value;
entry->last_access_time.store(new_time, std::memory_order_relaxed);
entry->tag.store(VALID_BIT | partial_hash,
std::memory_order_release);
return true;
}
continue;
}
if (current_tag & WRITING_BIT) {
LFC_CPU_RELAX();
current_tag = entry->tag.load(std::memory_order_relaxed);
continue;
}
if ((current_tag & HASH_MASK) == partial_hash) {
std::atomic_thread_fence(std::memory_order_acquire);
if (entry->key == key) {
uint64_t expected_tag = VALID_BIT | partial_hash;
uint64_t new_tag_val = WRITING_BIT | partial_hash;
if (entry->tag.compare_exchange_strong(expected_tag, new_tag_val,
std::memory_order_acq_rel)) {
entry->value = value;
entry->last_access_time.store(new_time,
std::memory_order_relaxed);
entry->tag.store(VALID_BIT | partial_hash,
std::memory_order_release);
return true;
}
current_tag = expected_tag;
continue;
}
}
break;
}
index = probe_next(index);
}
// Phase 2: LRU Eviction.
// **LIVELOCK FIX: The outer loop ensures we eventually succeed, even under
// high contention.**
while (true) {
size_t victim_index = 0;
uint64_t min_time = std::numeric_limits<uint64_t>::max();
for (size_t i = 0; i < TableSize; ++i) {
uint64_t current_tag = table_[i].tag.load(std::memory_order_acquire);
if ((current_tag & VALID_BIT) && !(current_tag & WRITING_BIT)) {
uint64_t access_time =
table_[i].last_access_time.load(std::memory_order_relaxed);
if (access_time < min_time) {
min_time = access_time;
victim_index = i;
}
}
}
Entry* victim_entry = &table_[victim_index];
uint64_t expected_tag = victim_entry->tag.load(std::memory_order_relaxed);
// **LIVELOCK FIX: We removed the check on last_access_time here.**
// We only check if the slot is still valid. If so, we proceed forcefully.
// If another thread writes or removes it, expected_tag will change and
// CAS will fail, causing a retry, which is correct.
if (!(expected_tag & VALID_BIT) || (expected_tag & WRITING_BIT)) {
continue; // The victim was already claimed or removed by another
// writer. Find a new one.
}
uint64_t new_tag = WRITING_BIT | partial_hash;
if (victim_entry->tag.compare_exchange_strong(
expected_tag, new_tag, std::memory_order_acq_rel)) {
// Success, we claimed the slot.
victim_entry->key = key;
victim_entry->value = value;
victim_entry->last_access_time.store(new_time,
std::memory_order_relaxed);
victim_entry->tag.store(VALID_BIT | partial_hash,
std::memory_order_release);
return true; // Exit the function
}
// CAS failed. This means another thread (writer or remover) changed the
// tag. The outer while(true) loop will cause us to retry the entire
// eviction process.
}
}
// get() and remove() methods are unchanged
std::optional<Value> get(const Key& key) {
uint64_t hash_val = hasher_(key);
size_t index = hash_val & (TableSize - 1);
uint64_t partial_hash = hash_val & HASH_MASK;
for (size_t i = 0; i < TableSize; ++i) {
Entry* entry = &table_[index];
uint64_t current_tag = entry->tag.load(std::memory_order_acquire);
while (true) {
if (current_tag == 0) {
return std::nullopt;
}
if (current_tag & WRITING_BIT) {
LFC_CPU_RELAX();
current_tag = entry->tag.load(std::memory_order_acquire);
continue;
}
if ((current_tag & HASH_MASK) == partial_hash) {
if (entry->key == key) {
uint64_t new_time =
access_timer_.fetch_add(1, std::memory_order_relaxed);
entry->last_access_time.store(new_time, std::memory_order_relaxed);
return entry->value;
}
}
break;
}
index = probe_next(index);
}
return std::nullopt;
}
bool remove(const Key& key) {
uint64_t hash_val = hasher_(key);
size_t index = hash_val & (TableSize - 1);
uint64_t partial_hash = hash_val & HASH_MASK;
for (size_t i = 0; i < TableSize; ++i) {
Entry* entry = &table_[index];
uint64_t current_tag = entry->tag.load(std::memory_order_acquire);
while (true) {
if (current_tag == 0) {
return false;
}
if (current_tag & WRITING_BIT) {
LFC_CPU_RELAX();
current_tag = entry->tag.load(std::memory_order_acquire);
continue;
}
if ((current_tag & HASH_MASK) == partial_hash) {
std::atomic_thread_fence(std::memory_order_acquire);
if (entry->key == key) {
uint64_t expected_tag = VALID_BIT | partial_hash;
if (entry->tag.compare_exchange_strong(expected_tag, 0,
std::memory_order_acq_rel)) {
return true;
}
current_tag = expected_tag;
continue;
}
}
break;
}
index = probe_next(index);
}
return false;
}
};
} // namespace lfc
#include <atomic>
#include <cassert>
#include <chrono>
#include <cstring>
#include <functional>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <random>
#include <thread>
#include <vector>
#include "LockFreeLruCache.hpp"
#ifdef _WIN32
#include <windows.h>
void set_console_color(int color) {
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
}
#endif
enum class Color { GREEN = 10, YELLOW = 14, RED = 12, BLUE = 9, DEFAULT = 7 };
void print_status(const std::string& msg, Color color) {
#ifdef _WIN32
set_console_color(static_cast<int>(color));
#else
const char* code = "\033[39m";
if (color == Color::GREEN)
code = "\033[32m";
else if (color == Color::YELLOW)
code = "\033[33m";
else if (color == Color::RED)
code = "\033[31m";
else if (color == Color::BLUE)
code = "\033[34m";
std::cout << code;
#endif
std::cout << msg;
#ifdef _WIN32
set_console_color(static_cast<int>(Color::DEFAULT));
#else
std::cout << "\033[39m";
#endif
std::cout << std::endl;
}
struct TriviallyCopyableString {
char data[16];
TriviallyCopyableString() { data[0] = '\0'; }
TriviallyCopyableString(const char* str) {
strncpy(data, str, sizeof(data) - 1);
data[sizeof(data) - 1] = '\0';
}
bool operator==(const char* other) const { return strcmp(data, other) == 0; }
};
void test_single_thread_lru() {
print_status("\n--- 正在运行单线程LRU逻辑验证 ---", Color::YELLOW);
lfc::LockFreeLruCache<int, TriviallyCopyableString, 4> cache;
print_status("[1] 填充缓存 (4个项目)...", Color::DEFAULT);
cache.put(1, "one");
cache.put(2, "two");
cache.put(3, "three");
cache.put(4, "four");
assert(cache.get(1).value_or(TriviallyCopyableString()) == "one");
assert(cache.get(2).value_or(TriviallyCopyableString()) == "two");
assert(cache.get(3).value_or(TriviallyCopyableString()) == "three");
assert(cache.get(4).value_or(TriviallyCopyableString()) == "four");
print_status(" ...缓存已满,所有项目已验证。", Color::GREEN);
print_status("[2] 访问键 2,使其成为最近使用的项...", Color::DEFAULT);
cache.get(2);
print_status("[3] 插入键 5,应淘汰键 1...", Color::DEFAULT);
cache.put(5, "five");
assert(!cache.get(1).has_value());
assert(cache.get(2).has_value());
assert(cache.get(3).has_value());
assert(cache.get(4).has_value());
assert(cache.get(5).has_value());
print_status(" ...已验证键 1 被淘汰,键 5 已添加。", Color::GREEN);
print_status("[4] 移除键 3...", Color::DEFAULT);
assert(cache.remove(3));
assert(!cache.get(3).has_value());
print_status(" ...已验证键 3 已被移除。", Color::GREEN);
assert(!cache.get(999).has_value());
print_status("[5] 已验证获取不存在的键返回nullopt。", Color::GREEN);
print_status("--- 单线程LRU测试通过 ---", Color::GREEN);
}
void test_multi_thread_lru_contention() {
print_status("\n--- 正在运行多线程LRU竞争测试 ---", Color::YELLOW);
const size_t CACHE_SIZE = 128;
const int HOT_KEYS = 32;
const int NUM_THREADS = 8;
lfc::LockFreeLruCache<int, int, CACHE_SIZE> cache;
std::atomic<bool> stop_signal{false};
print_status("[1] 预填充缓存至满容量...", Color::DEFAULT);
for (size_t i = 0; i < CACHE_SIZE; ++i) {
cache.put(i, i);
}
std::vector<std::thread> threads;
print_status("[2] 启动线程以重复访问 '热' 键 (0-31)...", Color::DEFAULT);
for (int i = 0; i < NUM_THREADS; ++i) {
threads.emplace_back([&, i]() {
// **CORRECTED LOGIC**: Each thread now accesses *random* hot keys
std::mt19937 rng(
std::chrono::steady_clock::now().time_since_epoch().count() + i);
std::uniform_int_distribution<int> dist(0, HOT_KEYS - 1);
while (!stop_signal.load()) {
int key_to_access = dist(rng);
cache.get(key_to_access);
std::this_thread::yield();
}
});
}
print_status("[3] 启动线程以连续插入新键,强制进行淘汰...", Color::DEFAULT);
threads.emplace_back([&]() {
int current_key = CACHE_SIZE;
while (!stop_signal.load()) {
cache.put(current_key, current_key);
current_key++;
std::this_thread::yield();
}
});
std::this_thread::sleep_for(std::chrono::seconds(3));
stop_signal.store(true);
for (auto& t : threads) {
t.join();
}
print_status("[4] 线程已完成,正在验证结果...", Color::DEFAULT);
int hot_keys_found = 0;
for (int i = 0; i < HOT_KEYS; ++i) {
if (cache.get(i).has_value()) {
hot_keys_found++;
}
}
print_status(" ...在缓存中找到的热键数量: " + std::to_string(hot_keys_found) +
" / " + std::to_string(HOT_KEYS),
Color::BLUE);
assert(hot_keys_found > HOT_KEYS * 0.95);
print_status("--- 多线程LRU竞争测试通过 ---", Color::GREEN);
}
void test_stress_concurrent_read_write() {
print_status("\n--- 正在运行并发读写及压力测试 ---", Color::YELLOW);
const size_t CACHE_SIZE = 8192;
const int NUM_WRITERS = 4;
const int NUM_READERS =
std::thread::hardware_concurrency() - NUM_WRITERS > 0
? std::thread::hardware_concurrency() - NUM_WRITERS
: 4;
const int OPS_PER_THREAD = 200000;
const int KEY_RANGE = OPS_PER_THREAD * NUM_WRITERS;
lfc::LockFreeLruCache<int, int, CACHE_SIZE> cache;
std::vector<std::thread> threads;
std::atomic<long long> total_ops{0};
print_status("[1] 启动 " + std::to_string(NUM_WRITERS) + " 个写入线程和 " +
std::to_string(NUM_READERS) + " 个读取线程...",
Color::DEFAULT);
auto start_time = std::chrono::high_resolution_clock::now();
for (int i = 0; i < NUM_WRITERS; ++i) {
threads.emplace_back([&, i]() {
for (int j = 0; j < OPS_PER_THREAD; ++j) {
int key = i * OPS_PER_THREAD + j;
cache.put(key, key);
}
total_ops += OPS_PER_THREAD;
});
}
for (int i = 0; i < NUM_READERS; ++i) {
threads.emplace_back([&]() {
std::mt19937 rng(
std::chrono::steady_clock::now().time_since_epoch().count());
std::uniform_int_distribution<int> dist(0, KEY_RANGE - 1);
for (int j = 0; j < OPS_PER_THREAD; ++j) {
int key = dist(rng);
auto result = cache.get(key);
if (result.has_value()) {
assert(result.value() == key);
}
}
total_ops += OPS_PER_THREAD;
});
}
for (auto& t : threads) {
t.join();
}
auto end_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = end_time - start_time;
print_status("[2] 所有线程已完成,未发生崩溃。", Color::GREEN);
long long final_total_ops = total_ops.load();
double ops_per_second = final_total_ops / duration.count();
std::cout << std::fixed << std::setprecision(2);
print_status(" 总操作数: " + std::to_string(final_total_ops), Color::BLUE);
print_status(" 执行时间: " + std::to_string(duration.count()) + " 秒",
Color::BLUE);
print_status(
" 吞吐率: " + std::to_string(ops_per_second / 1e6) + " 百万次操作/秒",
Color::BLUE);
print_status("--- 并发读写及压力测试通过 ---", Color::GREEN);
}
int main() {
try {
test_single_thread_lru();
test_multi_thread_lru_contention();
test_stress_concurrent_read_write();
} catch (const std::exception& e) {
print_status("发生异常: " + std::string(e.what()), Color::RED);
return 1;
} catch (...) {
print_status("发生未知异常。", Color::RED);
return 1;
}
print_status("\n所有测试均已成功完成!", Color::GREEN);
return 0;
}
zhixuan @gpu04 : ~/ Cache$ g++ - std =
c++ 17 - o cache_test main.cpp - pthread -
Wall zhixuan @gpu04 : ~/ Cache$./
cache_test
-- -
正在运行单线程LRU逻辑验证-- -
[1] 填充缓存(4个项目)...... 缓存已满,所有项目已验证。
[2] 访问键 2,使其成为最近使用的项...
[3] 插入键 5,应淘汰键 1... ... 已验证键 1 被淘汰,键 5 已添加。
[4] 移除键 3... ... 已验证键 3 已被移除。
[5] 已验证获取不存在的键返回nullopt。-- -
单线程LRU测试通过-- -
-- -正在运行多线程LRU竞争测试-- -
[1] 预填充缓存至满容量...[2] 启动线程以重复访问 '热' 键(
0 - 31)...[3] 启动线程以连续插入新键,强制进行淘汰...
[4] 线程已完成,正在验证结果...... 在缓存中找到的热键数量 : 32 /
32 -- -多线程LRU竞争测试通过-- -
-- -正在运行并发读写及压力测试-- -
[1] 启动 4 个写入线程和 92 个读取线程...[2] 所有线程已完成,未发生崩溃。
总操作数 : 19200000 执行时间 : 77.114936 秒 吞吐率
: 0.248979 百万次操作 /
秒-- -
并发读写及压力测试通过-- -
所有测试均已成功完成! zhixuan @gpu04 : ~/ Cache$
#pragma once
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <thread>
#include <type_traits>
// 在x86/x64架构下,用于在自旋等待中降低CPU功耗并提示超线程
#if defined(__i386__) || defined(__x86_64__)
#include <immintrin.h>
#define LFC_CPU_RELAX() _mm_pause()
#else
#define LFC_CPU_RELAX() std::this_thread::yield()
#endif
namespace lfc {
/**
* @brief 编译时FNV-1a哈希函数
* @param str 指向要哈希的字符数组的指针
* @param len 字符数组的长度
* @return 64位的哈希值
*/
constexpr uint64_t fnv1a_hash(const char* str, size_t len) noexcept {
uint64_t hash = 14695981039346656037ull;
for (size_t i = 0; i < len; ++i) {
hash ^= static_cast<uint64_t>(str[i]);
hash *= 1099511628211ull;
}
return hash;
}
/**
* @brief 默认的哈希器,适用于任何可平凡复制的类型
* @tparam Key 要哈希的键类型
*/
template <typename Key>
struct DefaultHasher {
uint64_t operator()(const Key& key) const noexcept {
static_assert(std::is_trivially_copyable_v<Key>,
"DefaultHasher requires a trivially copyable key.");
return fnv1a_hash(reinterpret_cast<const char*>(&key), sizeof(Key));
}
};
/**
* @brief
* 一个基于线性探测和标签位域的、线程安全的无锁哈希表(缓存),集成了Clock淘汰策略。
* @tparam Key 键的类型。必须是可平凡复制的 (trivially copyable)。
* @tparam Value 值的类型。必须是可平凡复制的 (trivially copyable)。
* @tparam TableSize 哈希表的大小。必须是2的幂。
* @tparam Hasher 用于计算键哈希值的函数对象。
*/
template <typename Key, typename Value, size_t TableSize = 4096,
typename Hasher = DefaultHasher<Key> >
class LockFreeCache {
static_assert((TableSize > 0) && ((TableSize & (TableSize - 1)) == 0),
"TableSize must be a power of 2");
static_assert(std::is_trivially_copyable_v<Key> &&
std::is_trivially_copyable_v<Value>,
"Key and Value must be trivially copyable for this lock-free "
"implementation.");
private:
// | 63: valid | 62: writing | 61: used | 60..0: hash |
static constexpr uint64_t VALID_BIT = 1ULL << 63;
static constexpr uint64_t WRITING_BIT = 1ULL << 62;
static constexpr uint64_t USED_BIT = 1ULL << 61;
static constexpr uint64_t HASH_MASK = USED_BIT - 1;
struct alignas(64) Entry {
std::atomic<uint64_t> tag;
Key key;
Value value;
};
std::array<Entry, TableSize> table;
Hasher hasher;
// 时钟指针,用于Clock淘汰算法
alignas(64) std::atomic<size_t> clock_hand_;
static size_t probe_next(size_t index) {
return (index + 1) & (TableSize - 1);
}
public:
LockFreeCache() : clock_hand_(0) {
for (auto& entry : table) {
entry.tag.store(0, std::memory_order_relaxed);
}
}
/**
* @brief
* 更新或插入一个键值对。如果缓存已满,则会尝试淘汰一个最近未使用的条目。
* @param key 要插入/更新的键
* @param value 要关联的值
* @return 成功则返回true,如果尝试淘汰后仍然失败则返回false。
*/
bool update(const Key& key, const Value& value) {
uint64_t hash_val = hasher(key);
size_t index = hash_val & (TableSize - 1);
uint64_t partial_hash = hash_val & HASH_MASK;
// 阶段1:尝试查找空位或更新现有键
for (size_t i = 0; i < TableSize; ++i) {
Entry* entry = &table[index];
uint64_t current_tag = entry->tag.load(std::memory_order_relaxed);
while (true) {
if (current_tag == 0) { // Case 1: 空槽位
uint64_t new_tag = WRITING_BIT | partial_hash;
if (entry->tag.compare_exchange_strong(current_tag, new_tag,
std::memory_order_acq_rel)) {
entry->key = key;
entry->value = value;
// 插入新数据时,将其标记为VALID和USED
entry->tag.store(VALID_BIT | USED_BIT | partial_hash,
std::memory_order_release);
return true;
}
continue;
}
if (current_tag & WRITING_BIT) { // Case 2: 正在写入
LFC_CPU_RELAX();
current_tag = entry->tag.load(std::memory_order_relaxed);
continue;
}
if ((current_tag & HASH_MASK) == partial_hash) { // Case 3: 有效槽位
std::atomic_thread_fence(std::memory_order_acquire);
if (entry->key == key) {
uint64_t expected_tag = current_tag; // 使用最新的tag值
uint64_t new_tag = WRITING_BIT | partial_hash;
if (entry->tag.compare_exchange_strong(expected_tag, new_tag,
std::memory_order_acq_rel)) {
entry->value = value;
// 更新数据后,标记为VALID和USED
entry->tag.store(VALID_BIT | USED_BIT | partial_hash,
std::memory_order_release);
return true;
}
current_tag = expected_tag;
continue;
}
}
// 哈希冲突或非空槽,探测下一个
break;
}
index = probe_next(index);
}
// 阶段2:如果上面失败了 (表可能满了),则启动Clock淘汰机制
constexpr size_t EVICTION_CANDIDATES = 20; // 每次尝试淘汰的候选数量
for (size_t i = 0; i < EVICTION_CANDIDATES; ++i) {
size_t evict_index =
clock_hand_.fetch_add(1, std::memory_order_relaxed) & (TableSize - 1);
Entry* entry = &table[evict_index];
uint64_t current_tag = entry->tag.load(std::memory_order_relaxed);
// 只淘汰有效的、未被写入的条目
if ((current_tag & VALID_BIT) && !(current_tag & WRITING_BIT)) {
if (current_tag & USED_BIT) {
// 如果使用位为1, 给他第二次机会,清零使用位
uint64_t new_tag = current_tag & ~USED_BIT;
entry->tag.compare_exchange_strong(current_tag, new_tag,
std::memory_order_relaxed);
} else {
// 如果使用位为0, 尝试淘汰它
uint64_t new_tag =
WRITING_BIT | (current_tag & HASH_MASK); // 保持旧的哈希
if (entry->tag.compare_exchange_strong(current_tag, new_tag,
std::memory_order_acq_rel)) {
// 成功锁定,现在可以安全地替换内容
entry->key = key;
entry->value = value;
entry->tag.store(VALID_BIT | USED_BIT | partial_hash,
std::memory_order_release);
return true;
}
}
}
}
return false; // 尝试淘汰后仍然失败
}
/**
* @brief 查找一个键。此操作是线程安全的。
* @param key 要查找的键
* @return 如果找到,返回包含值的std::optional,否则返回std::nullopt。
*/
std::optional<Value> get(const Key& key) const {
uint64_t hash_val = hasher(key);
size_t index = hash_val & (TableSize - 1);
uint64_t partial_hash = hash_val & HASH_MASK;
for (size_t i = 0; i < TableSize; ++i) {
// NOTE: get是const方法,所以需要const_cast来修改tag。
// 这是一个在并发数据结构中常见的做法。
// fetch_or是原子性的,因此线程安全。
Entry* entry = const_cast<Entry*>(&table[index]);
uint64_t current_tag = entry->tag.load(std::memory_order_acquire);
while (true) {
if (current_tag == 0) {
return std::nullopt;
}
if (current_tag & WRITING_BIT) {
LFC_CPU_RELAX();
current_tag = entry->tag.load(std::memory_order_acquire);
continue;
}
if ((current_tag & HASH_MASK) == partial_hash) {
if (entry->key == key) {
// 命中!原子地设置USED_BIT
if (!(current_tag & USED_BIT)) {
entry->tag.fetch_or(USED_BIT, std::memory_order_relaxed);
}
return entry->value;
}
}
break;
}
index = probe_next(index);
}
return std::nullopt;
}
};
} // namespace lfc