美文网首页
YYMemoryCache分析

YYMemoryCache分析

作者: honzon_0 | 来源:发表于2022-02-27 18:01 被阅读0次

YYMemoryCache主要分析

  • LRU算法+双链表+哈希表
  • 线程操作锁
  • cache内部变量内存释放队列
  • 哈希表保存节点与key。通过key直接能够取到节点避免循环遍历链表的时间开销
  • 基于双向链表特性。当新增,移动或者删除时可以避免循环遍历链表的时间开销
  • 内存警告时清除缓存

缓存的核心原则是所有操作快速,所以应该尽量避免所有的遍历数据操作

双向链表
@interface _YYLinkedMapNode : NSObject {
    @package
    __unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
    __unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
    id _key;
    id _value;
    ...
}
@end
哈希表与虚拟头节点
@interface _YYLinkedMap : NSObject {
    @package
    CFMutableDictionaryRef _dic; // do not set object directly
    ...
    _YYLinkedMapNode *_head; // MRU, do not change it directly
    _YYLinkedMapNode *_tail; // LRU, do not change it directly
    ...
}
LRU算法实现

新增节点添加到链表首位

- (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
    //...
    if (_head) {// 链表不为空
        //新增节点变为新的首节点
        node->_next = _head;
        _head->_prev = node;
        _head = node;
    } else {//链表为空
        _head = _tail = node;
    }
}

移动节点到首位

- (void)bringNodeToHead:(_YYLinkedMapNode *)node {
    //节点就在首位
    if (_head == node) return;
    
    //如果是尾节点
    if (_tail == node) {
        //尾节点更新
        _tail = node->_prev;
        _tail->_next = nil;
    } else {
        //节点前驱更新
        node->_next->_prev = node->_prev;
        //节点后继更新
        node->_prev->_next = node->_next;
    }
    
    //节点变更为首节点
    node->_next = _head;
    node->_prev = nil;
    _head->_prev = node;
    _head = node;
}

删除节点

- (void)removeNode:(_YYLinkedMapNode *)node {
    ...
    //更新后继节点的前驱
    if (node->_next) node->_next->_prev = node->_prev;
    //更新前驱节点的后继
    if (node->_prev) node->_prev->_next = node->_next;
    //指定新的首节点
    if (_head == node) _head = node->_next;
    //指定新的尾节点
    if (_tail == node) _tail = node->_prev;
}

LRU算法是最近最先使用,那么存在着最远最少使用淘汰,即需要删除尾节点操作

- (_YYLinkedMapNode *)removeTailNode {
    //空链表
    if (!_tail) return nil;
    
    _YYLinkedMapNode *tail = _tail;
    //...
    //一个节点
    if (_head == _tail) {
        _head = _tail = nil;
    } else {//多个节点
        //尾节点更新即可
        _tail = _tail->_prev;
        _tail->_next = nil;
    }
    return tail;
}
线程操作锁

最开始是自旋锁OSSpinLock

OSSpinLock _lock;

_lock = OS_SPINLOCK_INIT;//忙等待锁,线程反复检查锁变量是否可用,不会挂起

OSSpinLockLock(&_lock);
//Code..
OSSpinLockUnlock(&_lock);

后因为自旋锁有优先级反转问题,因此改为互斥锁

互斥锁 - 每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象

pthread_mutex_t _lock;

pthread_mutex_init(&_lock, NULL); //互斥锁  

pthread_mutex_lock(&_lock);
//Code
pthread_mutex_unlock(&_lock);

线程优先级反转

共享资源Data
任务线程Task1 优先级Low
任务线程Taks2 优先级High
自旋锁Lock 

1. Task1访问Data  Lock加锁
2. Task2访问Data  等待锁释放,因为忙等,不会挂起并且Task2优先级高,占用大量CPU时间
3. Task1需要的CPU时间不够,任务无法完成,Lock无法解开
4. 形成短暂死锁
cache内部变量内存释放队列

cache内部用到的对象内存在异步线程释放,减少主线程压力

// 一个对象释放常驻队列
static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() {
    return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
}

xxx   ...
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
    [xxx func]; //利用block将对象捕获,在异步线程释放
});
内存警告时清除缓存
// 系统通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];


//双链表清空
- (void)removeAll {
    ...
    //头节点清空
    _head = nil;
    //尾节点清空
    _tail = nil;
    if (CFDictionaryGetCount(_dic) > 0) {
        //临时变量,防止丢失
        CFMutableDictionaryRef holder = _dic;
        //清空哈希表
        _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        
        //
        if (_releaseAsynchronously) {//异步线程释放
            dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
            dispatch_async(queue, ^{
                CFRelease(holder); // hold and release in specified queue
            });
        } else if (_releaseOnMainThread && !pthread_main_np()) {// 主线程释放
            dispatch_async(dispatch_get_main_queue(), ^{
                CFRelease(holder); // hold and release in specified queue
            });
        } else {
            CFRelease(holder);//当前线程释放
        }
    }
}

相关文章

  • YYMemoryCache分析

    YYMemoryCache主要分析 LRU算法+双链表+哈希表 线程操作锁 cache内部变量内存释放队列 哈希表...

  • YYCache源码分析(二)

    本文分析YYMemoryCache实现原理: YYMemoryCache是内存缓存,所以存取速度非常快,主要用到两...

  • iOS源码解析—YYCache(YYDiskCache)

    概述 上一篇主要讲解了YYMemoryCache的文件结构,分析了YYMemoryCache类的相关方法,本章主要...

  • YYMemoryCache源码分析

    YYCache 缓存框架 YYMemoryCache 内存中的缓存 YYDiskCache YYKVStorage...

  • YYMemoryCache的源码分析

    本人有若干成套学习视频, 可试看! 可试看! 可试看, 重要的事情说三遍 包含Java, 数据结构与算法, iOS...

  • YYCache源码分析

    YYMemoryCache YYMemoryCache用于对内存缓存进行管理,与SDWebImage对于内存缓存管...

  • YYMemoryCache

    YYMemoryCache是内存缓存,所以存取速度非常快,主要用到两种数据结构的LRU淘汰算法 1.LRU Cac...

  • YYMemoryCache解析、总结

    YYMemoryCache解析、总结

  • YYCache源码简析

    作者设计思路 1.YYMemoryCache YYMemoryCache负责管理内存缓存。这个类是线程安全的。 L...

  • iOS开发进阶:三方源码解读

    一、YYMemoryCache的源码解读 YYMemoryCache是用来做内存管理的类,他支持设置缓存对象的个数...

网友评论

      本文标题:YYMemoryCache分析

      本文链接:https://www.haomeiwen.com/subject/myihrrtx.html