YYCache是一個(gè)線程安全的高性能鍵值緩存組件沦补,代碼質(zhì)量很高,他的作者是國內(nèi)開發(fā)者ibireme開發(fā)并且開源的. 下面就來簡(jiǎn)單剖析一下他的源碼.如有哪里錯(cuò)誤希望拍磚!多多交流多多進(jìn)步!謝謝!!!
基本使用方法
//要緩存的對(duì)象
NSString*name =@"JJCoderMa";
//需要緩存的對(duì)象在緩存里對(duì)應(yīng)的鍵
NSString *key =@"username";
//創(chuàng)建一個(gè)YYCache實(shí)例:userInfoCache
YYCache *userInfoCache = [YYCache cacheWithName:@"userInfocache"];
//存入鍵值對(duì)
[userInfoCache setObject:userName forKey:key withBlock:^{
NSLog(@"caching object succeed");
}];
//判斷緩存是否存在
[userInfoCache containsObjectForKey:key withBlock:^(NSString* _Nonnull key,BOOLcontains) {
if(contains){
NSLog(@"存在!");
}
}];
//根據(jù)key讀取數(shù)據(jù)
[userInfoCache objectForKey:key withBlock:^(NSString* _Nonnull key,id _Nonnull object) {
NSLog(@"user_name : %@",object);
}];
//根據(jù)key移除緩存
[userInfoCache removeObjectForKey:key withBlock:^(NSString* _Nonnull key) {
NSLog(@"remove user name %@",key);
}];
//移除所有緩存
[userInfoCache removeAllObjectsWithBlock:^{
NSLog(@"removing all cache succeed");
}];
//移除所有緩存帶進(jìn)度
[userInfoCache removeAllObjectsWithProgressBlock:^(intremovedCount,inttotalCount) {
NSLog(@"remove all cache objects: removedCount :%d totalCount : %d",removedCount,totalCount);
} endBlock:^(BOOLerror) {
if(!error){
NSLog(@"remove all cache objects: succeed");
}else{
NSLog(@"remove all cache objects: failed");
}
}];
```swift
YYCache 架構(gòu)圖: (第一次嘗試使用MindNode,圖畫的不好~~~~)
- YYCache:提供了最外層的接口拔妥,調(diào)用了YYMemoryCache與YYDiskCache的相關(guān)方法。
- YYMemoryCache:負(fù)責(zé)處理容量小旬牲,相對(duì)高速的內(nèi)存緩存。線程安全,支持自動(dòng)和手動(dòng)清理緩存等功能澡绩。
- _YYLinkedMap:YYMemoryCache使用的雙向鏈表類列疗。
- _YYLinkedMapNode:是_YYLinkedMap使用的節(jié)點(diǎn)類滑蚯。
- YYDiskCache:負(fù)責(zé)處理容量大,相對(duì)低速的磁盤緩存抵栈。線程安全告材,支持異步操作,自動(dòng)和手動(dòng)清理緩存等功能古劲。
- YYKVStorage:YYDiskCache的底層實(shí)現(xiàn)類斥赋,用于管理磁盤緩存。
- YYKVStorageItem:內(nèi)置在YYKVStorage中产艾,是YYKVStorage內(nèi)部用于封裝某個(gè)緩存的類
YYCache給用戶提供所有最外層的緩存操作接口灿渴,而這些接口的內(nèi)部?jī)?nèi)部實(shí)際上是調(diào)用了YYMemoryCache和YYDiskCache對(duì)象的相關(guān)方法。
因?yàn)閅YMemoryCache和YYDiskCache的實(shí)例作為YYCache的兩個(gè)公開的屬性胰舆,所以用戶無法直接使用YYMemoryCache和YYDiskCache對(duì)象骚露,只能通過屬性的方式來間接使用它們。
YYCache的部分屬性和接口
YYCache的接口實(shí)現(xiàn)
從上面的接口實(shí)現(xiàn)可以看出:在YYCache中缚窿,永遠(yuǎn)都是先訪問內(nèi)存緩存鬼佣,然后再訪問磁盤緩存(包括了寫入翔试,讀取,查詢晒夹,刪除緩存的操作)。而且關(guān)于內(nèi)存緩存(_memoryCache)的操作婉烟,是不存在block回調(diào)的
YYMemoryCache
YYMemoryCache 操作類似于NSCache,它將需要緩存的對(duì)象與傳入的key關(guān)聯(lián)起來。
YYMemoryCache的內(nèi)部有:
緩存淘汰算法:使用LRU(least-recently-used) 算法來淘汰(清理)使用頻率較低的緩存。
緩存清理策略:使用三個(gè)維度來標(biāo)記育瓜,分別是count(緩存數(shù)量),cost(開銷)栽烂,age(距上一次的訪問時(shí)間)躏仇。YYMemoryCache提供了分別針對(duì)這三個(gè)維度的清理緩存的接口。用戶可以根據(jù)不同的需求(策略)來清理在某一維度超標(biāo)的緩存腺办。
緩存淘汰算法的目的在于區(qū)分出使用頻率高和使用頻率低的緩存焰手,當(dāng)緩存數(shù)量達(dá)到一定限制的時(shí)候會(huì)優(yōu)先清理那些使用頻率低的緩存。因?yàn)槭褂妙l率已經(jīng)比較低的緩存在將來的使用頻率也很有可能會(huì)低怀喉。
YYMemoryCache用一個(gè)鏈表節(jié)點(diǎn)類來保存某個(gè)單獨(dú)的內(nèi)存緩存的信息(鍵书妻,值,緩存時(shí)間等)躬拢,然后用一個(gè)雙向鏈表類來保存和管理這些節(jié)點(diǎn)躲履。這兩個(gè)類的名稱分別是:
_YYLinkedMapNode:鏈表內(nèi)的節(jié)點(diǎn)類,可以看做是對(duì)某個(gè)單獨(dú)內(nèi)存緩存的封裝聊闯。
_YYLinkedMap:雙向鏈表類工猜,用于保存和管理所有內(nèi)存緩存(節(jié)點(diǎn))
_YYLinkedMapNode可以被看做是對(duì)某個(gè)緩存的封裝:它包含了該節(jié)點(diǎn)上一個(gè)和下一個(gè)節(jié)點(diǎn)的指針,以及緩存的key
和對(duì)應(yīng)的值(對(duì)象)馅袁,還有該緩存的開銷和訪問時(shí)間域慷。
/**
A node in linked map.
Typically, you should not use this class directly.
鏈表內(nèi)的節(jié)點(diǎn)類,可以看做是對(duì)某個(gè)單獨(dú)內(nèi)存緩存的封裝
*/
@interface _YYLinkedMapNode : NSObject {
@package
__unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
__unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
id _key;
id _value;
NSUInteger _cost;
NSTimeInterval _time;
}
@end
_YYLinkedMap:
@interface _YYLinkedMap : NSObject {
@package
CFMutableDictionaryRef _dic; // 用于存放節(jié)點(diǎn)
NSUInteger _totalCost; //總開銷
NSUInteger _totalCount; //節(jié)點(diǎn)總數(shù)
_YYLinkedMapNode *_head; // 鏈表的頭部結(jié)點(diǎn)
_YYLinkedMapNode *_tail; // 鏈表的尾部節(jié)點(diǎn)
BOOL _releaseOnMainThread; //是否在主線程釋放汗销,默認(rèn)為NO
BOOL _releaseAsynchronously; //是否在子線程釋放犹褒,默認(rèn)為YES
}
//在鏈表頭部插入某節(jié)點(diǎn)
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;
//將鏈表內(nèi)部的某個(gè)節(jié)點(diǎn)移到鏈表頭部
- (void)bringNodeToHead:(_YYLinkedMapNode *)node;
//移除某個(gè)節(jié)點(diǎn)
- (void)removeNode:(_YYLinkedMapNode *)node;
//移除鏈表的尾部節(jié)點(diǎn)并返回它
- (_YYLinkedMapNode *)removeTailNode;
//移除所有節(jié)點(diǎn)(默認(rèn)在子線程操作)
- (void)removeAll;
@end
CFMutableDictionaryRef,用于保存節(jié)點(diǎn)的鍵值對(duì)弛针,它還持有了鏈表內(nèi)節(jié)點(diǎn)的總開銷叠骑,總數(shù)量,頭尾節(jié)點(diǎn)等數(shù)據(jù)削茁。
[圖片上傳失敗...(image-875de1-1522245469574)]
_YYLinkedMap實(shí)現(xiàn)細(xì)節(jié):
插入結(jié)點(diǎn)(復(fù)習(xí)雙向鏈表的時(shí)候~~~~)
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
//設(shè)置該node的值
CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
//增加開銷和總緩存數(shù)量
_totalCost += node->_cost;
_totalCount++;
if (_head) {
//如果鏈表內(nèi)已經(jīng)存在頭節(jié)點(diǎn)宙枷,則將這個(gè)頭節(jié)點(diǎn)賦給當(dāng)前節(jié)點(diǎn)的尾指針(原第一個(gè)節(jié)點(diǎn)變成了現(xiàn)第二個(gè)節(jié)點(diǎn))
node->_next = _head;
//將該節(jié)點(diǎn)賦給現(xiàn)第二個(gè)節(jié)點(diǎn)的頭指針(此時(shí)_head指向的節(jié)點(diǎn)是先第二個(gè)節(jié)點(diǎn))
_head->_prev = node;
//將該節(jié)點(diǎn)賦給鏈表的頭結(jié)點(diǎn)指針(該節(jié)點(diǎn)變成了現(xiàn)第一個(gè)節(jié)點(diǎn))
_head = node;
} else {
//如果鏈表內(nèi)沒有頭結(jié)點(diǎn),說明是空鏈表茧跋。說明是第一次插入慰丛,則將鏈表的頭尾節(jié)點(diǎn)都設(shè)置為當(dāng)前節(jié)點(diǎn)
_head = _tail = node;
}
}
在雙向鏈表中:
- 每個(gè)節(jié)點(diǎn)都有兩個(gè)分別指向前后節(jié)點(diǎn)的指針。所以說每個(gè)節(jié)點(diǎn)都知道它前一個(gè)節(jié)點(diǎn)和后一個(gè)節(jié)點(diǎn)是誰瘾杭。
- 鏈表的頭部節(jié)點(diǎn)指向它前面節(jié)點(diǎn)的指針為空诅病;鏈表尾部節(jié)點(diǎn)指向它后側(cè)節(jié)點(diǎn)的指針也為空
將某個(gè)節(jié)點(diǎn)移動(dòng)到表頭
- (void)bringNodeToHead:(_YYLinkedMapNode *)node {
//如果該節(jié)點(diǎn)已經(jīng)是鏈表頭部節(jié)點(diǎn),則立即返回,不做任何操作
if (_head == node) return;
if (_tail == node) {
//如果該節(jié)點(diǎn)是鏈表的尾部節(jié)點(diǎn)
//1. 將該節(jié)點(diǎn)的頭指針指向的節(jié)點(diǎn)變成鏈表的尾節(jié)點(diǎn)(將倒數(shù)第二個(gè)節(jié)點(diǎn)變成倒數(shù)第一個(gè)節(jié)點(diǎn)贤笆,即尾部節(jié)點(diǎn))
_tail = node->_prev;
//2. 將新的尾部節(jié)點(diǎn)的尾部指針置空
_tail->_next = nil;
} else {
//如果該節(jié)點(diǎn)是鏈表頭部和尾部以外的節(jié)點(diǎn)(中間節(jié)點(diǎn))
//1. 將該node的頭指針指向的節(jié)點(diǎn)賦給其尾指針指向的節(jié)點(diǎn)的頭指針
node->_next->_prev = node->_prev;
//2. 將該node的尾指針指向的節(jié)點(diǎn)賦給其頭指針指向的節(jié)點(diǎn)的尾指針
node->_prev->_next = node->_next;
}
//將原頭節(jié)點(diǎn)賦給該節(jié)點(diǎn)的尾指針(原第一個(gè)節(jié)點(diǎn)變成了現(xiàn)第二個(gè)節(jié)點(diǎn))
node->_next = _head;
//將當(dāng)前節(jié)點(diǎn)的頭節(jié)點(diǎn)置空
node->_prev = nil;
//將現(xiàn)第二個(gè)節(jié)點(diǎn)的頭結(jié)點(diǎn)指向當(dāng)前節(jié)點(diǎn)(此時(shí)_head指向的節(jié)點(diǎn)是現(xiàn)第二個(gè)節(jié)點(diǎn))
_head->_prev = node;
//將該節(jié)點(diǎn)設(shè)置為鏈表的頭節(jié)點(diǎn)
_head = node;
}
移除鏈表中的某個(gè)節(jié)點(diǎn):
- (void)removeNode:(_YYLinkedMapNode *)node {
//除去該node的鍵對(duì)應(yīng)的值
CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
//減去開銷和總緩存數(shù)量
_totalCost -= node->_cost;
_totalCount--;
//節(jié)點(diǎn)操作
//1. 將該node的頭指針指向的節(jié)點(diǎn)賦給其尾指針指向的節(jié)點(diǎn)的頭指針
if (node->_next) node->_next->_prev = node->_prev;
//2. 將該node的尾指針指向的節(jié)點(diǎn)賦給其頭指針指向的節(jié)點(diǎn)的尾指針
if (node->_prev) node->_prev->_next = node->_next;
//3. 如果該node就是鏈表的頭結(jié)點(diǎn)蝇棉,則將該node的尾部指針指向的節(jié)點(diǎn)賦給鏈表的頭節(jié)點(diǎn)(第二變成了第一)
if (_head == node) _head = node->_next;
//4. 如果該node就是鏈表的尾節(jié)點(diǎn),則將該node的頭部指針指向的節(jié)點(diǎn)賦給鏈表的尾節(jié)點(diǎn)(倒數(shù)第二變成了倒數(shù)第一)
if (_tail == node) _tail = node->_prev;
}
查找/緩存/更新某個(gè)對(duì)象
//是否包含某個(gè)緩存對(duì)象
- (BOOL)containsObjectForKey:(id)key {
//嘗試從內(nèi)置的字典中獲得緩存對(duì)象
if (!key) return NO;
pthread_mutex_lock(&_lock);
BOOL contains = CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key));
pthread_mutex_unlock(&_lock);
return contains;
}
//獲取某個(gè)緩存對(duì)象
- (id)objectForKey:(id)key {
if (!key) return nil;
pthread_mutex_lock(&_lock);
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
if (node) {
//如果節(jié)點(diǎn)存在芥永,則更新它的時(shí)間信息(最后一次訪問的時(shí)間)
node->_time = CACurrentMediaTime();
[_lru bringNodeToHead:node];
}
pthread_mutex_unlock(&_lock);
return node ? node->_value : nil;
}
//寫入某個(gè)緩存對(duì)象篡殷,開銷默認(rèn)為0
- (void)setObject:(id)object forKey:(id)key {
[self setObject:object forKey:key withCost:0];
}
//寫入某個(gè)緩存對(duì)象,并存入緩存開銷
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
if (!key) return;
if (!object) {
[self removeObjectForKey:key];
return;
}
pthread_mutex_lock(&_lock);
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
NSTimeInterval now = CACurrentMediaTime();
if (node) {
//如果存在與傳入的key值匹配的node埋涧,則更新該node的value,cost,time板辽,并將這個(gè)node移到鏈表頭部
//更新總cost
_lru->_totalCost -= node->_cost;
_lru->_totalCost += cost;
//更新node
node->_cost = cost;
node->_time = now;
node->_value = object;
//將node移動(dòng)至鏈表頭部
[_lru bringNodeToHead:node];
} else {
//如果不存在與傳入的key值匹配的node,則新建一個(gè)node飞袋,將key,value,cost,time賦給它戳气,并將這個(gè)node插入到鏈表頭部
//新建node,并賦值
node = [_YYLinkedMapNode new];
node->_cost = cost;
node->_time = now;
node->_key = key;
node->_value = object;
//將node插入至鏈表頭部
[_lru insertNodeAtHead:node];
}
//如果cost超過了限制链患,則進(jìn)行刪除緩存操作(從鏈表尾部開始刪除巧鸭,直到符合限制要求)
if (_lru->_totalCost > _costLimit) {
dispatch_async(_queue, ^{
[self trimToCost:_costLimit];
});
}
//如果total count超過了限制,則進(jìn)行刪除緩存操作(從鏈表尾部開始刪除麻捻,刪除一次即可)
if (_lru->_totalCount > _countLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (_lru->_releaseAsynchronously) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[node class]; //hold and release in queue
});
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
[node class]; //hold and release in queue
});
}
}
pthread_mutex_unlock(&_lock);
}
//移除某個(gè)緩存對(duì)象
- (void)removeObjectForKey:(id)key {
if (!key) return;
pthread_mutex_lock(&_lock);
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
if (node) {
//內(nèi)部調(diào)用了鏈表的removeNode:方法
[_lru removeNode:node];
if (_lru->_releaseAsynchronously) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[node class]; //hold and release in queue
});
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
[node class]; //hold and release in queue
});
}
}
pthread_mutex_unlock(&_lock);
}
//內(nèi)部調(diào)用了鏈表的removeAll方法
- (void)removeAllObjects {
pthread_mutex_lock(&_lock);
[_lru removeAll];
pthread_mutex_unlock(&_lock);
}
YYDiskCache
YYDiskCache處理容量大纲仍,相對(duì)低速的磁盤緩存。線程安全贸毕,支持異步操作郑叠。作為YYCache的第二級(jí)緩存,
與第一級(jí)緩存YYMemoryCache的相同點(diǎn)是:
- 都具有查詢明棍,寫入乡革,讀取,刪除緩存的接口摊腋。
- 不直接操作緩存沸版,也是間接地通過另一個(gè)類(YYKVStorage)來操作緩存。
- 它使用LRU算法來清理緩存兴蒸。
- 支持按 cost视粮,count 和 age 這三個(gè)維度來清理不符合標(biāo)準(zhǔn)的緩存。
它與YYMemoryCache不同點(diǎn)是:
- 根據(jù)緩存數(shù)據(jù)的大小來采取不同的形式的緩存:
- 數(shù)據(jù)庫sqlite: 針對(duì)小容量緩存橙凳,緩存的data和元數(shù)據(jù)都保存在數(shù)據(jù)庫里蕾殴。
- 除了 cost,count 和 age 三個(gè)維度之外岛啸,還添加了一個(gè)磁盤容量的維度钓觉。
// 緩存方式
typedef NS_ENUM(NSUInteger, YYKVStorageType) {
YYKVStorageTypeFile = 0,
YYKVStorageTypeSQLite = 1,
YYKVStorageTypeMixed = 2,
};
緩存的大致邏輯
- 首先判斷傳入的key和value是否符合要求,如果不符合要求坚踩,則立即返回NO荡灾,緩存失敗。
- 再判斷是否type==YYKVStorageTypeFile并且文件名為空字符串(或nil):如果是,則立即返回NO卧晓,緩存失敗芬首。
判斷filename是否為空字符串:
- 如果不為空:
寫入文件,并將緩存的key逼裆,等信息寫入數(shù)據(jù)庫郁稍,但是不將key對(duì)應(yīng)的data寫入數(shù)據(jù)庫。 - 如果為空:
如果緩存類型為YYKVStorageTypeSQLite:將緩存文件刪除
如果緩存類型不為YYKVStorageTypeSQLite:則將緩存的key和對(duì)應(yīng)的data等其他信息存入數(shù)據(jù)庫胜宇。
YYKVStorage
YYKVStorage實(shí)例負(fù)責(zé)保存和管理所有磁盤緩存耀怜。和YYMemoryCache里面的_YYLinkedMap將緩存封裝成節(jié)點(diǎn)類_YYLinkedMapNode類似,YYKVStorage也將某個(gè)單獨(dú)的磁盤緩存封裝成了一個(gè)類桐愉,這個(gè)類就是YYKVStorageItem财破,它保存了某個(gè)緩存所對(duì)應(yīng)的一些信息(key, value, 文件名,大小等等):
YYKVStorageItem結(jié)構(gòu)包含了鍵/值/文件名/值大小/時(shí)間戳/擴(kuò)展數(shù)據(jù)等等字段
/**
YYKVStorageItem is used by `YYKVStorage` to store key-value pair and meta data.
Typically, you should not use this class directly.
YYKVStorage也將某個(gè)單獨(dú)的磁盤緩存封裝成了一個(gè)類从诲,這個(gè)類就是YYKVStorageItem左痢,它保存了某個(gè)緩存所對(duì)應(yīng)的一些信息(key, value, 文件名,大小等等)
*/
@interface YYKVStorageItem : NSObject
@property (nonatomic, strong) NSString *key; ///< key
@property (nonatomic, strong) NSData *value; ///< value
@property (nullable, nonatomic, strong) NSString *filename; ///< filename (nil if inline)
@property (nonatomic) int size; ///< value's size in bytes
@property (nonatomic) int modTime; ///< modification unix timestamp
@property (nonatomic) int accessTime; ///< last access unix timestamp
@property (nullable, nonatomic, strong) NSData *extendedData; ///< extended data (nil if no extended data)
@end
還有一些方法來操作數(shù)據(jù)
//YYKVStorage.h
- (BOOL)saveItem:(YYKVStorageItem *)item;
- (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value;
- (BOOL)saveItemWithKey:(NSString *)key
value:(NSData *)value
filename:(nullable NSString *)filename
extendedData:(nullable NSData *)extendedData;
#pragma mark - Remove Items
- (BOOL)removeItemForKey:(NSString *)key;
- (BOOL)removeItemForKeys:(NSArray<NSString *> *)keys;
- (BOOL)removeItemsLargerThanSize:(int)size;
- (BOOL)removeItemsEarlierThanTime:(int)time;
- (BOOL)removeItemsToFitSize:(int)maxSize;
- (BOOL)removeItemsToFitCount:(int)maxCount;
總結(jié)一下:
1. 為什么使用雙向鏈表?
- 雙向鏈表可以知道前后節(jié)點(diǎn),所以如果想移動(dòng)其中一個(gè)節(jié)點(diǎn)的話系洛,其前后的節(jié)點(diǎn)不好做銜接俊性。
- 其節(jié)點(diǎn)的關(guān)聯(lián)僅僅是靠指針,所以對(duì)于插入和刪除操作會(huì)很便利描扯,而類似數(shù)組的方式尋址操作缺比較費(fèi)時(shí)定页。由于在LRU策略中會(huì)有非常多的移動(dòng),插入和刪除節(jié)點(diǎn)的操作绽诚,使用雙向鏈表是比較有優(yōu)勢(shì)的典徊。
2. 使用CFDictionary而沒有用NSDictionary來實(shí)現(xiàn)?
CFDictionary 更加底層,更快.
3. 為什么內(nèi)存緩存使用互斥鎖?而磁盤緩存使用信號(hào)量?
作者在最初使用的是自旋鎖(OSSpinLock)作為內(nèi)存緩存的線程鎖恩够,但是后來得知其不夠安全卒落,所以退而求其次,使用了pthread_mutex 這篇文章有說明
網(wǎng)上說:但當(dāng)信號(hào)總量設(shè)為 1 時(shí)也可以當(dāng)作鎖來玫鸟。在沒有等待情況出現(xiàn)時(shí)导绷,它的性能比 pthread_mutex 還要高,但一旦有等待情況出現(xiàn)時(shí)屎飘,性能就會(huì)下降許多妥曲。相對(duì)于 OSSpinLock 來說,它的優(yōu)勢(shì)在于等待時(shí)不會(huì)消耗 CPU 資源钦购。對(duì)磁盤緩存來說檐盟,它比較合適。