揭秘Android MMKV线程安全机制:从源码到实践的深度解析
在移动应用开发中,高性能且线程安全的数据存储是一项基础而关键的需求。MMKV作为一款专为移动平台设计的高性能键值存储框架,凭借其卓越的线程安全机制在Android开发中得到广泛应用。本文将深入剖析Android MMKV的源码,全面解析其线程安全机制的设计理念、实现细节及最佳实践。
一、线程安全机制概述
1.1 多线程环境下的数据竞争问题
在多线程环境中,多个线程同时访问和修改共享数据可能导致数据竞争(Data Race)问题,如脏读、不可重复读和幻读等。在存储系统中,这些问题可能导致数据不一致、程序崩溃等严重后果。
// 非线程安全的简单存储示例(仅作说明用)
public class UnsafeStorage {
private Map<String, String> data = new HashMap<>();
// 写入数据(非线程安全)
public void put(String key, String value) {
data.put(key, value);
// 假设这里有其他操作,可能被多线程干扰
if (data.size() % 10 == 0) {
saveToDisk();
}
}
// 读取数据(非线程安全)
public String get(String key) {
return data.get(key);
}
// 保存到磁盘
private void saveToDisk() {
// 将数据写入磁盘的操作
}
}
在上述示例中,如果多个线程同时调用put方法,可能会导致data.size()计算错误,或者在保存到磁盘时数据不一致。
1.2 MMKV的线程安全设计目标
MMKV的线程安全机制旨在解决多线程环境下的数据竞争问题,确保:
- 数据一致性:所有线程看到的数据状态一致
- 操作原子性:读写操作是原子的,不会被其他线程中断
- 高性能:在保证线程安全的同时,不引入过大的性能开销
- 死锁避免:避免线程间因争夺锁资源而导致的死锁
1.3 线程安全机制的核心组件
MMKV的线程安全机制主要基于以下几个核心组件实现:
- 互斥锁(Mutex):用于保护关键资源,确保同一时间只有一个线程可以访问
- 读写锁(ReadWriteLock):允许多个线程同时读取,但写操作互斥
- 原子操作(Atomic Operations):用于轻量级同步,如引用计数、状态标志等
- 内存屏障(Memory Barrier):确保内存操作的顺序性和可见性
二、互斥锁机制的实现
2.1 基本互斥锁的使用
MMKV在关键操作中使用互斥锁来保护共享资源,确保同一时间只有一个线程可以执行关键代码段。
// MMKV.cpp
// 初始化互斥锁
MMKV::MMKV(const std::string &path, MMKVMode mode) : m_path(path), m_mode(mode) {
// 初始化互斥锁属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
// 设置为递归锁,允许同一线程多次加锁
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化互斥锁
pthread_mutex_init(&m_lock, &attr);
pthread_mutexattr_destroy(&attr);
// 其他初始化操作
initialize();
}
// 写入数据的操作,使用互斥锁保护
bool MMKV::setString(const std::string &key, const std::string &value) {
// 加锁,确保同一时间只有一个线程可以执行以下代码
SCOPED_LOCK(m_lock);
// 计算数据大小
size_t size = computeSize(key, value);
// 确保有足够的空间
if (!ensureSize(size)) {
return false;
}
// 写入数据
return writeString(key, value);
}
// 读取数据的操作,使用互斥锁保护
bool MMKV::getString(const std::string &key, std::string &value) {
// 加锁,确保同一时间只有一个线程可以执行以下代码
SCOPED_LOCK(m_lock);
// 查找数据
size_t offset = findKey(key);
if (offset == INVALID_OFFSET) {
return false;
}
// 读取数据
return readString(offset, value);
}
2.2 递归锁的设计与应用
MMKV使用递归锁(Recursive Mutex)允许同一线程多次获取同一把锁而不会死锁。
// MMKV.cpp
// 递归锁的使用示例
bool MMKV::batchDelete(const std::vector<std::string> &keys) {
// 第一次加锁
SCOPED_LOCK(m_lock);
// 批量删除操作
bool result = true;
for (const auto &key : keys) {
// 可能会调用其他也需要加锁的方法
if (!deleteKey(key)) {
result = false;
}
}
return result;
}
// 可能被batchDelete调用的方法
bool MMKV::deleteKey(const std::string &key) {
// 同一线程再次加锁(递归锁允许这样做)
SCOPED_LOCK(m_lock);
// 查找数据
size_t offset = findKey(key);
if (offset == INVALID_OFFSET) {
return false;
}
// 标记数据为已删除
markAsDeleted(offset);
return true;
}
递归锁的设计使得MMKV的内部方法调用更加灵活,避免了因锁的嵌套调用而导致的死锁问题。
2.3 锁的粒度控制
MMKV在设计上非常注重锁的粒度控制,只在必要的代码段加锁,减少锁的持有时间,从而提高并发性能。
// MMKV.cpp
// 优化锁粒度的示例
bool MMKV::containsKey(const std::string &key) {
// 只在关键操作时加锁
{
SCOPED_LOCK(m_lock);
// 查找数据
return findKey(key) != INVALID_OFFSET;
} // 锁在此处释放
// 锁释放后执行其他操作
notifyKeyCheck(key);
return true;
}
在上述示例中,锁只保护了findKey这个关键操作,而后续的notifyKeyCheck方法调用则在锁外执行,这样可以减少锁的持有时间,提高并发度。
三、读写锁机制的实现
3.1 读写锁的基本原理
读写锁允许多个线程同时进行读操作,但在写操作时会互斥,确保写操作的原子性和数据一致性。
// ReadWriteLock.h
// 读写锁的实现
class ReadWriteLock {
private:
pthread_rwlock_t m_rwlock; // POSIX读写锁
public:
ReadWriteLock() {
// 初始化读写锁
pthread_rwlock_init(&m_rwlock, nullptr);
}
~ReadWriteLock() {
// 销毁读写锁
pthread_rwlock_destroy(&m_rwlock);
}
// 获取读锁
void lockRead() {
pthread_rwlock_rdlock(&m_rwlock);
}
// 释放读锁
void unlockRead() {
pthread_rwlock_unlock(&m_rwlock);
}
// 获取写锁
void lockWrite() {
pthread_rwlock_wrlock(&m_rwlock);
}
// 释放写锁
void unlockWrite() {
pthread_rwlock_unlock(&m_rwlock);
}
};
3.2 MMKV中的读写锁应用
MMKV在数据读写操作中使用读写锁来提高并发性能,允许多个线程同时读取数据。
// MMKV.cpp
// 使用读写锁的示例
class MMKV {
private:
ReadWriteLock m_rwLock; // 读写锁
// 其他成员变量
public:
// 读取数据的操作,使用读锁
bool getString(const std::string &key, std::string &value) {
// 获取读锁,允许多个线程同时读取
SCOPED_READ_LOCK(m_rwLock);
// 查找数据
size_t offset = findKey(key);
if (offset == INVALID_OFFSET) {
return false;
}
// 读取数据
return readString(offset, value);
}
// 写入数据的操作,使用写锁
bool setString(const std::string &key, const std::string &value) {
// 获取写锁,确保同一时间只有一个线程可以写入
SCOPED_WRITE_LOCK(m_rwLock);
// 计算数据大小
size_t size = computeSize(key, value);
// 确保有足够的空间
if (!ensureSize(size)) {
return false;
}
// 写入数据
return writeString(key, value);
}
};
3.3 读写锁的公平性与性能平衡
MMKV在实现读写锁时,需要平衡公平性和性能:
// ReadWriteLock.h (改进版)
class ReadWriteLock {
private:
pthread_rwlock_t m_rwlock;
bool m_fairness; // 公平性标志
public:
ReadWriteLock(bool fairness = false) : m_fairness(fairness) {
// 初始化读写锁
pthread_rwlockattr_t attr;
pthread_rwlockattr_init(&attr);
// 设置公平性
if (fairness) {
// 某些系统支持读写锁的公平性设置
// 注意:并非所有系统都支持此选项
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
}
pthread_rwlock_init(&m_rwlock, &attr);
pthread_rwlockattr_destroy(&attr);
}
// 其他方法保持不变
};
在实际应用中,MMKV默认使用非公平锁以获得更好的性能,因为在大多数场景下,读操作远远多于写操作,非公平锁可以减少线程切换开销。
四、原子操作与内存屏障
4.1 原子操作的基本实现
MMKV使用原子操作来实现轻量级同步,避免使用重量级锁带来的性能开销。
// Atomic.h
// 原子操作的实现
template <typename T>
class Atomic {
private:
std::atomic<T> m_value; // C++11标准原子类型
public:
Atomic(T initialValue = T()) : m_value(initialValue) {}
// 获取原子值
T load() const {
return m_value.load(std::memory_order_acquire);
}
// 设置原子值
void store(T newValue) {
m_value.store(newValue, std::memory_order_release);
}
// 原子比较并交换
bool compareExchange(T &expected, T desired) {
return m_value.compare_exchange_weak(expected, desired,
std::memory_order_acq_rel,
std::memory_order_acquire);
}
// 原子递增
T increment() {
return m_value.fetch_add(1, std::memory_order_acq_rel) + 1;
}
// 原子递减
T decrement() {
return m_value.fetch_sub(1, std::memory_order_acq_rel) - 1;
}
};
4.2 MMKV中的原子操作应用
MMKV在多个场景下使用原子操作,如引用计数、状态标志等。
// MMKV.cpp
// 使用原子操作的示例
class MMKV {
private:
Atomic<int> m_refCount; // 引用计数,使用原子操作
Atomic<bool> m_isClosed; // 关闭状态标志,使用原子操作
public:
// 增加引用计数
void retain() {
m_refCount.increment();
}
// 减少引用计数,当计数为0时关闭
void release() {
if (m_refCount.decrement() == 0) {
close();
}
}
// 关闭操作
void close() {
// 使用原子操作检查是否已关闭
bool expected = false;
if (m_isClosed.compareExchange(expected, true)) {
// 执行关闭操作
doClose();
}
}
};
4.3 内存屏障的使用
MMKV使用内存屏障确保内存操作的顺序性和可见性,特别是在多线程环境下。
// MemoryBarrier.h
// 内存屏障的实现
class MemoryBarrier {
public:
// 全内存屏障,确保所有内存操作的顺序
static void full() {
// 使用C++11的内存屏障
std::atomic_thread_fence(std::memory_order_seq_cst);
}
// 获取内存屏障,确保后续的内存读取操作不会被重排到屏障之前
static void acquire() {
std::atomic_thread_fence(std::memory_order_acquire);
}
// 释放内存屏障,确保之前的内存写入操作不会被重排到屏障之后
static void release() {
std::atomic_thread_fence(std::memory_order_release);
}
};
// MMKV.cpp
// 内存屏障的应用示例
bool MMKV::flush() {
// 确保所有内存写入操作完成
MemoryBarrier::release();
// 执行文件同步操作
bool result = fsync(m_fd) == 0;
// 确保后续的内存读取操作能看到最新数据
MemoryBarrier::acquire();
return result;
}
五、线程安全的初始化与销毁
5.1 单例模式的线程安全实现
MMKV使用线程安全的单例模式来管理全局实例。
// MMKV.h
// 线程安全的单例模式实现
class MMKV {
private:
static std::mutex s_instanceLock; // 单例锁
static std::unordered_map<std::string, MMKV *> s_allInstances; // 所有实例
// 私有构造函数
MMKV(const std::string &path, MMKVMode mode);
// 禁止拷贝和赋值
MMKV(const MMKV &) = delete;
MMKV &operator=(const MMKV &) = delete;
public:
// 获取单例实例
static MMKV *defaultMMKV(MMKVMode mode = MMKV_SINGLE_PROCESS) {
static std::once_flag s_onceFlag;
static std::string s_defaultPath = getDefaultMMKVPath();
// 使用std::call_once确保只初始化一次
std::call_once(s_onceFlag, []() {
// 创建默认实例
MMKV *kv = new MMKV(s_defaultPath, MMKV_SINGLE_PROCESS);
s_allInstances[s_defaultPath] = kv;
});
// 从map中获取实例(已确保存在)
return s_allInstances[s_defaultPath];
}
// 获取命名实例
static MMKV *mmkvWithID(const std::string &mmapID, MMKVMode mode = MMKV_SINGLE_PROCESS) {
std::lock_guard<std::mutex> lock(s_instanceLock);
// 检查实例是否已存在
auto it = s_allInstances.find(mmapID);
if (it != s_allInstances.end()) {
return it->second;
}
// 创建新实例
std::string path = getMMKVPath(mmapID);
MMKV *kv = new MMKV(path, mode);
s_allInstances[mmapID] = kv;
return kv;
}
};
5.2 资源的线程安全销毁
MMKV在销毁时需要确保资源的正确释放,避免多线程环境下的竞态条件。
// MMKV.cpp
// 线程安全的资源销毁
MMKV::~MMKV() {
// 确保在销毁前所有操作已完成
SCOPED_LOCK(m_lock);
// 关闭文件描述符
if (m_fd >= 0) {
close(m_fd);
m_fd = -1;
}
// 解除内存映射
if (m_ptr && m_size > 0) {
munmap(m_ptr, m_size);
m_ptr = nullptr;
m_size = 0;
}
// 清理其他资源
clearAllData();
}
// 安全关闭所有实例
void MMKV::closeAll() {
std::lock_guard<std::mutex> lock(s_instanceLock);
// 创建实例的副本,避免在遍历过程中修改map
std::vector<MMKV *> instances;
for (auto &pair : s_allInstances) {
instances.push_back(pair.second);
}
// 关闭所有实例
for (auto kv : instances) {
delete kv;
}
// 清空实例map
s_allInstances.clear();
}
六、多进程环境下的线程安全
6.1 进程间同步机制
在多进程环境下,MMKV使用文件锁来实现进程间的同步。
// FileLock.h
// 文件锁的实现
class FileLock {
private:
int m_fd; // 文件描述符
public:
explicit FileLock(int fd) : m_fd(fd) {}
// 获取共享锁(读锁)
bool lockShared() {
struct flock lock;
lock.l_type = F_RDLCK; // 读锁
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0; // 锁定整个文件
// 使用F_SETLKW进行阻塞式锁定
return fcntl(m_fd, F_SETLKW, &lock) == 0;
}
// 获取排他锁(写锁)
bool lockExclusive() {
struct flock lock;
lock.l_type = F_WRLCK; // 写锁
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0; // 锁定整个文件
// 使用F_SETLKW进行阻塞式锁定
return fcntl(m_fd, F_SETLKW, &lock) == 0;
}
// 解锁
bool unlock() {
struct flock lock;
lock.l_type = F_UNLCK; // 解锁
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0; // 解锁整个文件
return fcntl(m_fd, F_SETLK, &lock) == 0;
}
};
6.2 多进程模式下的实现
MMKV在多进程模式下,通过文件锁和版本号机制确保数据的一致性。
// MMKV.cpp
// 多进程模式下的初始化
MMKV::MMKV(const std::string &path, MMKVMode mode) : m_path(path), m_mode(mode) {
// 打开文件
m_fd = open(path.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (m_fd < 0) {
// 处理错误
return;
}
// 初始化文件锁
m_fileLock = new FileLock(m_fd);
// 在多进程模式下,获取排他锁进行初始化
if (mode & MMKV_MULTI_PROCESS) {
m_fileLock->lockExclusive();
// 初始化或加载文件内容
initialize();
// 释放排他锁
m_fileLock->unlock();
} else {
// 单进程模式直接初始化
initialize();
}
}
// 多进程模式下的写操作
bool MMKV::setString(const std::string &key, const std::string &value) {
// 在多进程模式下,获取排他锁
if (m_mode & MMKV_MULTI_PROCESS) {
m_fileLock->lockExclusive();
}
// 使用内部锁保护写操作
SCOPED_LOCK(m_lock);
// 执行写操作
bool result = doSetString(key, value);
// 增加版本号,表示数据已更新
if (result) {
m_version++;
// 写入版本号到文件
writeVersion();
}
// 释放排他锁
if (m_mode & MMKV_MULTI_PROCESS) {
m_fileLock->unlock();
}
return result;
}
// 多进程模式下的读操作
bool MMKV::getString(const std::string &key, std::string &value) {
// 在多进程模式下,获取共享锁
if (m_mode & MMKV_MULTI_PROCESS) {
m_fileLock->lockShared();
}
// 使用内部锁保护读操作
SCOPED_LOCK(m_lock);
// 检查版本号,必要时重新加载数据
checkVersionAndReload();
// 执行读操作
bool result = doGetString(key, value);
// 释放共享锁
if (m_mode & MMKV_MULTI_PROCESS) {
m_fileLock->unlock();
}
return result;
}
七、线程安全的批量操作
7.1 批量操作的原子性保证
MMKV通过锁机制确保批量操作的原子性。
// MMKV.cpp
// 批量写入操作
bool MMKV::batchPut(const std::vector<std::pair<std::string, std::string>> &items) {
// 获取写锁,确保操作的原子性
SCOPED_WRITE_LOCK(m_rwLock);
// 计算总大小
size_t totalSize = 0;
for (const auto &item : items) {
totalSize += computeSize(item.first, item.second);
}
// 确保有足够空间
if (!ensureSize(totalSize)) {
return false;
}
// 批量写入数据
for (const auto &item : items) {
if (!writeString(item.first, item.second)) {
return false;
}
}
return true;
}
// 批量读取操作
bool MMKV::batchGet(const std::vector<std::string> &keys,
std::vector<std::pair<std::string, std::string>> &values) {
// 获取读锁,允许多个线程同时读取
SCOPED_READ_LOCK(m_rwLock);
// 批量读取数据
for (const auto &key : keys) {
std::string value;
if (getString(key, value)) {
values.push_back({key, value});
}
}
return true;
}
7.2 事务机制的实现
MMKV通过临时存储和一次性提交实现简单的事务机制。
// MMKV.cpp
// 事务操作
class MMKVTransaction {
private:
MMKV *m_mmkv;
std::vector<std::pair<std::string, std::string>> m_pendingWrites;
std::vector<std::string> m_pendingDeletes;
bool m_active;
public:
explicit MMKVTransaction(MMKV *mmkv) : m_mmkv(mmkv), m_active(true) {}
// 添加写入操作
void put(const std::string &key, const std::string &value) {
if (!m_active) {
return;
}
// 检查是否已在待删除列表中
auto it = std::find(m_pendingDeletes.begin(), m_pendingDeletes.end(), key);
if (it != m_pendingDeletes.end()) {
m_pendingDeletes.erase(it);
}
// 添加到待写入列表
m_pendingWrites.push_back({key, value});
}
// 添加删除操作
void remove(const std::string &key) {
if (!m_active) {
return;
}
// 检查是否已在待写入列表中
for (auto it = m_pendingWrites.begin(); it != m_pendingWrites.end(); ++it) {
if (it->first == key) {
m_pendingWrites.erase(it);
break;
}
}
// 添加到待删除列表
m_pendingDeletes.push_back(key);
}
// 提交事务
bool commit() {
if (!m_active) {
return false;
}
// 获取写锁
SCOPED_WRITE_LOCK(m_mmkv->m_rwLock);
// 执行所有待写入操作
for (const auto &item : m_pendingWrites) {
if (!m_mmkv->setString(item.first, item.second)) {
return false;
}
}
// 执行所有待删除操作
for (const auto &key : m_pendingDeletes) {
if (!m_mmkv->deleteKey(key)) {
return false;
}
}
// 事务完成
m_active = false;
return true;
}
// 回滚事务
void rollback() {
m_active = false;
m_pendingWrites.clear();
m_pendingDeletes.clear();
}
~MMKVTransaction() {
if (m_active) {
rollback();
}
}
};
八、线程安全的缓存机制
8.1 缓存的线程安全设计
MMKV的缓存机制在多线程环境下需要保证数据一致性。
// MMKV.cpp
// 线程安全的缓存实现
class MMKV {
private:
// 缓存项结构
struct CacheItem {
std::string value;
time_t lastAccessTime;
size_t accessCount;
};
// 缓存映射表
std::unordered_map<std::string, CacheItem> m_cache;
// 缓存锁
mutable std::mutex m_cacheLock;
// 最大缓存项数
size_t m_maxCacheItems;
// 从缓存获取数据
bool getFromCache(const std::string &key, std::string &value) const {
std::lock_guard<std::mutex> lock(m_cacheLock);
auto it = m_cache.find(key);
if (it != m_cache.end()) {
// 更新访问时间和访问计数
it->second.lastAccessTime = time(nullptr);
it->second.accessCount++;
value = it->second.value;
return true;
}
return false;
}
// 添加数据到缓存
void addToCache(const std::string &key, const std::string &value) {
std::lock_guard<std::mutex> lock(m_cacheLock);
// 检查缓存是否已满
if (m_cache.size() >= m_maxCacheItems) {
// 移除最不常用的项
evictLeastUsedItem();
}
// 添加新项
CacheItem item;
item.value = value;
item.lastAccessTime = time(nullptr);
item.accessCount = 1;
m_cache[key] = item;
}
// 移除最不常用的缓存项
void evictLeastUsedItem() {
if (m_cache.empty()) {
return;
}
// 找到最不常用的项
auto it = m_cache.begin();
auto leastUsed = it;
for (; it != m_cache.end(); ++it) {
if (it->second.lastAccessTime < leastUsed->second.lastAccessTime ||
(it->second.lastAccessTime == leastUsed->second.lastAccessTime &&
it->second.accessCount < leastUsed->second.accessCount)) {
leastUsed = it;
}
}
// 移除最不常用的项
m_cache.erase(leastUsed);
}
};
8.2 缓存与主存储的同步
MMKV确保缓存与主存储之间的数据一致性。
// MMKV.cpp
// 线程安全的读取操作,包含缓存逻辑
bool MMKV::getString(const std::string &key, std::string &value) {
// 先尝试从缓存获取
if (getFromCache(key, value)) {
return true;
}
// 缓存未命中,从主存储读取
SCOPED_READ_LOCK(m_rwLock);
// 查找数据
size_t offset = findKey(key);
if (offset == INVALID_OFFSET) {
return false;
}
// 读取数据
if (!readString(offset, value)) {
return false;
}
// 添加到缓存
addToCache(key, value);
return true;
}
// 线程安全的写入操作,包含缓存逻辑
bool MMKV::setString(const std::string &key, const std::string &value) {
SCOPED_WRITE_LOCK(m_rwLock);
// 计算数据大小
size_t size = computeSize(key, value);
// 确保有足够的空间
if (!ensureSize(size)) {
return false;
}
// 写入数据
if (!writeString(key, value)) {
return false;
}
// 更新缓存
addToCache(key, value);
return true;
}
九、线程安全的异常处理
9.1 异常安全的锁管理
MMKV使用RAII(资源获取即初始化)技术确保锁的异常安全性。
// LockGuard.h
// 基于RAII的锁管理类
template <typename LockType>
class LockGuard {
private:
LockType &m_lock;
bool m_locked;
public:
explicit LockGuard(LockType &lock) : m_lock(lock), m_locked(true) {
m_lock.lock();
}
~LockGuard() {
if (m_locked) {
m_lock.unlock();
}
}
// 禁止拷贝和赋值
LockGuard(const LockGuard &) = delete;
LockGuard &operator=(const LockGuard &) = delete;
// 提前释放锁
void unlock() {
if (m_locked) {
m_lock.unlock();
m_locked = false;
}
}
};
// MMKV.cpp
// 使用LockGuard确保锁的异常安全
bool MMKV::deleteKey(const std::string &key) {
// 使用LockGuard管理锁,确保锁在作用域结束时自动释放
LockGuard<std::mutex> lock(m_lock);
// 查找数据
size_t offset = findKey(key);
if (offset == INVALID_OFFSET) {
return false;
}
// 执行可能抛出异常的操作
if (!doDeleteKey(offset)) {
return false;
}
// 从缓存中移除
removeFromCache(key);
return true;
}
9.2 异常情况下的资源保护
MMKV在异常情况下确保资源的正确释放和状态的一致性。
// MMKV.cpp
// 异常安全的文件操作
bool MMKV::writeString(const std::string &key, const std::string &value) {
// 使用RAII管理文件操作
FileOperationGuard guard(this);
// 写入key长度
if (!writeUInt32((uint32_t) key.size())) {
throw std::runtime_error("Failed to write key length");
}
// 写入key
if (!writeBytes(key.data(), key.size())) {
throw std::runtime_error("Failed to write key");
}
// 写入value长度
if (!writeUInt32((uint32_t) value.size())) {
throw std::runtime_error("Failed to write value length");
}
// 写入value
if (!writeBytes(value.data(), value.size())) {
throw std::runtime_error("Failed to write value");
}
// 操作成功,提交
guard.commit();
return true;
}
// FileOperationGuard.h
// 文件操作的RAII保护类
class FileOperationGuard {
private:
MMKV *m_mmkv;
bool m_committed;
size_t m_originalSize;
public:
explicit FileOperationGuard(MMKV *mmkv) : m_mmkv(mmkv), m_committed(false) {
// 保存原始大小,用于回滚
m_originalSize = m_mmkv->m_usedSize;
}
~FileOperationGuard() {
if (!m_committed) {
// 操作失败,回滚
m_mmkv->rollback(m_originalSize);
}
}
void commit() {
m_committed = true;
}
// 禁止拷贝和赋值
FileOperationGuard(const FileOperationGuard &) = delete;
FileOperationGuard &operator=(const FileOperationGuard &) = delete;
};
十、线程安全机制的性能优化
10.1 锁粒度的优化
MMKV通过减小锁的粒度来提高并发性能。
// MMKV.cpp
// 优化锁粒度的示例
bool MMKV::containsKey(const std::string &key) {
// 只在关键操作时加锁
bool result;
{
SCOPED_READ_LOCK(m_rwLock);
// 查找数据
result = findKey(key) != INVALID_OFFSET;
} // 锁在此处释放
// 锁释放后执行其他操作
if (result) {
// 更新统计信息(不需要锁保护)
updateHitCount(key);
} else {
updateMissCount(key);
}
return result;
}
10.2 无锁数据结构的应用
MMKV在某些场景下使用无锁数据结构来避免锁的开销。
// ConcurrentHashMap.h
// 线程安全的无锁哈希表
template <typename K, typename V>
class ConcurrentHashMap {
private:
struct Node {
K key;
V value;
std::atomic<Node *> next;
Node(const K &k, const V &v) : key(k), value(v), next(nullptr) {}
};
std::vector<std::atomic<Node *>> m_buckets;
size_t m_capacity;
public:
explicit ConcurrentHashMap(size_t capacity = 1024) : m_capacity(capacity) {
m_buckets.resize(capacity);
for (size_t i = 0; i < capacity; ++i) {
m_buckets[i].store(nullptr);
}
}
// 无锁插入操作
bool insert(const K &key, const V &value) {
size_t index = hash(key) % m_capacity;
Node *newNode = new Node(key, value);
// 使用原子操作实现无锁插入
Node *oldHead = m_buckets[index].load();
do {
newNode->next = oldHead;
} while (!m_buckets[index].compare_exchange_weak(oldHead, newNode));
return true;
}
// 无锁查找操作
bool find(const K &key, V &value) const {
size_t index = hash(key) % m_capacity;
Node *node = m_buckets[index].load();
while (node) {
if (node->key == key) {
value = node->value;
return true;
}
node = node->next.load();
}
return false;
}
// 哈希函数
size_t hash(const K &key) const {
// 使用标准库的哈希函数
return std::hash<K>()(key);
}
};
十一、线程安全机制的测试与验证
11.1 多线程测试框架
MMKV使用专门的多线程测试框架来验证线程安全机制。
// ThreadTest.h
// 多线程测试框架
class ThreadTest {
private:
std::vector<std::thread> m_threads;
std::function<void(int)> m_testFunction;
int m_threadCount;
public:
ThreadTest(const std::function<void(int)> &testFunction, int threadCount = std::thread::hardware_concurrency())
: m_testFunction(testFunction), m_threadCount(threadCount) {}
// 运行测试
void run() {
// 创建并启动线程
for (int i = 0; i < m_threadCount; ++i) {
m_threads.emplace_back([this, i]() {
m_testFunction(i);
});
}
// 等待所有线程完成
for (auto &thread : m_threads)