MMKV 一个重要特性就是增加了 android 侧对跨进程读写的支持,我们单独用一篇文章来分析一下 MMKV 对跨进程存储的实现方式
典型的初始化调用如下:
1 2 3 4
| MMKV mmkv = MMKV.mmkvWithID(MMKV_ID, MMKV.MULTI_PROCESS_MODE, CryptKey);
mmkv.encode(...) mmkv.decode(...)
|
MMKV 会把每个文件都通过 mmap 到进程的访问空间,因此每个进程本身就可以直接访问存储,但这里没有解决跨进程的并发访问问题,解决并发需要别的手段,我们看看 MMKV.MULTI_PROCESS_MODE 造成了什么影响
MMKV.MULTI_PROCESS_MODE
参考初始化流程的代码和MMKV.MULTI_PROCESS_MODE=2
, 来到 MMKV 构造函数, 可以发现此时 MMKV::m_isInterProcess=true, 这个对代码逻辑有什么影响呢?
- checkLoadData 的后半段代码会执行
- m_sharedProcessLock, m_exclusiveProcessLock 全部 enable
先来看看 checkLoadData 的后半段代码是啥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| if (!m_isInterProcess) { return; }
if (!m_metaFile->isFileValid()) { return; }
MMKVMetaInfo metaInfo; metaInfo.read(m_metaFile->getMemory()); if (m_metaInfo->m_sequence != metaInfo.m_sequence) { MMKVInfo("[%s] oldSeq %u, newSeq %u", m_mmapID.c_str(), m_metaInfo->m_sequence, metaInfo.m_sequence); SCOPED_LOCK(m_sharedProcessLock);
clearMemoryCache(); loadFromFile(); notifyContentChanged(); } else if (m_metaInfo->m_crcDigest != metaInfo.m_crcDigest) { MMKVDebug("[%s] oldCrc %u, newCrc %u, new actualSize %u", m_mmapID.c_str(), m_metaInfo->m_crcDigest, metaInfo.m_crcDigest, metaInfo.m_actualSize); SCOPED_LOCK(m_sharedProcessLock);
size_t fileSize = m_file->getActualFileSize(); if (m_file->getFileSize() != fileSize) { MMKVInfo("file size has changed [%s] from %zu to %zu", m_mmapID.c_str(), m_file->getFileSize(), fileSize); clearMemoryCache(); loadFromFile(); } else { partialLoadFromFile(); } notifyContentChanged(); }
|
相比较于单进程,这里增加了对内存中的 meta 和文件的 meta 的 seq 和 crc 校验比较,如果不相等,则需要走文件重新加载逻辑,这一块,和https://github.com/Tencent/MMKV/wiki/android_ipc#%E7%8A%B6%E6%80%81%E5%90%8C%E6%AD%A5描述是一致的
m_sharedProcessLock, m_exclusiveProcessLock 全部 enable 应该就是使锁生效,那么分析一下这个锁是如何实现跨进程锁的
InterProcessLock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class InterProcessLock { FileLock *m_fileLock; LockType m_lockType;
public: InterProcessLock(FileLock *fileLock, LockType lockType) : m_fileLock(fileLock), m_lockType(lockType), m_enable(true) { MMKV_ASSERT(m_fileLock); }
bool m_enable;
void lock() { if (m_enable) { m_fileLock->lock(m_lockType); } }
}
|
可以看到,MMKV 的跨进程锁是基于文件锁实现的, InterProcessLock#lock
基于FileLock.lock
实现, 一路转发来到FileLock::platformLock()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| bool FileLock::doLock(LockType lockType, bool wait) { if (!isFileLockValid()) { return false; } bool unLockFirstIfNeeded = false;
if (lockType == SharedLockType) { if (m_sharedLockCount > 0 || m_exclusiveLockCount > 0) { m_sharedLockCount++; return true; } } else { if (m_exclusiveLockCount > 0) { m_exclusiveLockCount++; return true; } if (m_sharedLockCount > 0) { unLockFirstIfNeeded = true; } }
auto ret = platformLock(lockType, wait, unLockFirstIfNeeded); if (ret) { if (lockType == SharedLockType) { m_sharedLockCount++; } else { m_exclusiveLockCount++; } } return ret; }
bool FileLock::platformLock(LockType lockType, bool wait, bool unLockFirstIfNeeded) { # ifdef MMKV_ANDROID if (m_isAshmem) { return ashmemLock(lockType, wait, unLockFirstIfNeeded); } # endif auto realLockType = LockType2FlockType(lockType); auto cmd = wait ? realLockType : (realLockType | LOCK_NB); if (unLockFirstIfNeeded) { auto ret = flock(m_fd, realLockType | LOCK_NB); if (ret == 0) { return true; } ret = flock(m_fd, LOCK_UN); if (ret != 0) { MMKVError("fail to try unlock first fd=%d, ret=%d, error:%s", m_fd, ret, strerror(errno)); } }
auto ret = flock(m_fd, cmd); if (ret != 0) { MMKVError("fail to lock fd=%d, ret=%d, error:%s", m_fd, ret, strerror(errno)); if (unLockFirstIfNeeded) { ret = flock(m_fd, LockType2FlockType(SharedLockType)); if (ret != 0) { MMKVError("fail to recover shared-lock fd=%d, ret=%d, error:%s", m_fd, ret, strerror(errno)); } } return false; } else { return true; } }
|
可以看到 MMKV 在文件锁 flock 的基础上,增加了一套计数器机制(代码中的 m_sharedLockCount 和 m_exclusiveLockCount),来确保
- 锁的重入特性
- 锁的共享和互斥性转换特性
关于文件锁的细节,可以参考https://gavv.github.io/articles/file-locks