OC底層原理21-鎖的原理

iOS--OC底層原理文章匯總

本文探索常用鎖以及@synchronized底層的原理。

鎖的分類

在開發(fā)中奕谭,使用最常見的恐怕就是@synchronized(互斥鎖)稳强、NSLock(互斥鎖)厂抖、以及dispatch_semaphore(信號(hào)量)溯街。其實(shí)還有許多種匹中,總分類有:互斥鎖、自旋鎖朱浴,細(xì)分之下多出了: 讀寫鎖吊圾、遞歸鎖达椰、條件鎖、信號(hào)量街夭,后三者是對(duì)基本鎖的上層封裝砰碴。先介紹幾個(gè)概念躏筏。

自旋鎖】是用于多線程同步的一種鎖板丽,線程反復(fù)檢查鎖變量是否可用(即可重入特性)。由于線程在這一過程中保持執(zhí)行趁尼,因此是一種忙等待埃碱。一旦獲取了自旋鎖,線程會(huì)一直保持該鎖酥泞,直至顯式釋放自旋鎖砚殿。 自旋鎖避免了進(jìn)程上下文的調(diào)度開銷,因此對(duì)于線程只會(huì)阻塞很短時(shí)間的場(chǎng)合是有效的芝囤。

【互斥鎖】是一種用于多線程編程中似炎,防止兩條線程同時(shí)對(duì)同一公共資源(比如全局變量)進(jìn)行讀寫的機(jī)制。該目的通過將代碼切片成一個(gè)一個(gè)的臨界區(qū)而達(dá)成悯姊。

遞歸鎖(recursive_mutext_t)是一種特殊的互斥鎖羡藐。

【讀寫鎖】是計(jì)算機(jī)程序的并發(fā)控制的一種同步機(jī)制(也稱“共享-互斥鎖”、多讀-單寫鎖) 用于解決多線程對(duì)公共資源讀寫問題悯许。讀的操作可并發(fā)重入仆嗦,寫操作是互斥的。 讀寫鎖通常用互斥鎖先壕、條件變量瘩扼、信號(hào)量實(shí)現(xiàn)。

【信號(hào)量】是一種更高級(jí)的同步機(jī)制垃僚,互斥鎖可以說是semaphore在僅取值0/1時(shí)的特例集绰。信號(hào)量可以有更多的取值空間,用來實(shí)現(xiàn)更加復(fù)雜的同步谆棺,而不單單是線程間互斥栽燕。

條件鎖】:條件鎖就是條件變量,當(dāng)進(jìn)程的某些資源要求不滿足時(shí)就進(jìn)入休眠包券,即鎖住了纫谅,當(dāng)資源被分配到了,條件鎖打開了溅固,進(jìn)程繼續(xù)運(yùn)行付秕。

對(duì)應(yīng)有以下鎖:
  1. OSSpinLock(自旋鎖)
  2. dispatch_semaphone(信號(hào)量)
  3. pthread_mutex(互斥鎖)
  4. NSLock(互斥鎖)
  5. NSCondition(條件鎖)
  6. os_unfair_lock (互斥鎖)
  7. pthread_mutex(recursive 互斥遞歸鎖)
  8. NSRecursiveLock(遞歸鎖)
  9. NSConditionLock(條件鎖)
  10. synchronized(互斥遞歸鎖)

OSSpinLock(自旋鎖)

  • 與互斥鎖(阻塞-睡眠)不同,自旋鎖加鎖后是進(jìn)入忙等狀態(tài)侍郭。
  • 如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了询吴,線程會(huì)以忙等的方式等待鎖掠河,一旦被訪問的資源被解鎖,則等待資源的線程會(huì)立即執(zhí)行猛计。

OSSpinLock效率很高唠摹,但是已不再安全。如果一個(gè)低優(yōu)先級(jí)的線程獲得鎖并訪問共享資源奉瘤,這時(shí)一個(gè)高優(yōu)先級(jí)的線程也嘗試獲得這個(gè)鎖勾拉,它會(huì)處于 spin lock 的忙等狀態(tài)從而占用大量 CPU。此時(shí)低優(yōu)先級(jí)線程無法與高優(yōu)先級(jí)線程爭(zhēng)奪 CPU 時(shí)間盗温,從而導(dǎo)致任務(wù)遲遲完不成藕赞、無法釋放 lock。

ibireme 大神--<不再安全的 OSSpinLock>

各類型鎖的性能

所以蘋果已經(jīng)推薦使用os_unfair_lock卖局。

  • os_unfair_lock基本使用
    關(guān)于os_unfair_lock是蘋果在iOS10之后推出斧蜕,它屬于互斥鎖,os_unfair_lock加鎖會(huì)讓等待的線程進(jìn)入休眠狀態(tài)砚偶,而不是忙等批销。這樣就提高了安全也降低了性能損耗。
#import <os/lock.h>
// 創(chuàng)建一個(gè) os_unfair_lock_t 鎖
os_unfair_lock_t unfairLock;
// 先分配此類型的變量并將其初始化為OS_UNFAIR_LOCK_INIT
unfairLock = &(OS_UNFAIR_LOCK_INIT);
// 嘗試加鎖染坯,返回YES or NO
os_unfair_lock_trylock(unfairLock)
// 加鎖
os_unfair_lock_lock(unfairLock);
// 解鎖
os_unfair_lock_unlock(unfairLock);

dispatch_semaphone(信號(hào)量)

信號(hào)量適用于異步線程同步操作的場(chǎng)景均芽。

    // 創(chuàng)建使用
    dispatch_semaphore_create(long value); // 創(chuàng)建信號(hào)量
    dispatch_semaphore_signal(dispatch_semaphore_t deem); // 發(fā)送信號(hào)量
    dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); // 等待信號(hào)量
    //  ??注意: 發(fā)送信號(hào)量和信號(hào)等待是成對(duì)出現(xiàn)

    // 常見使用場(chǎng)景
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{ // ①
        
        NSLog(@"任務(wù)1:%@",[NSThread currentThread]);
        dispatch_semaphore_signal(sem); // ③
    });
    
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // ②
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任務(wù)2:%@",[NSThread currentThread]);
        dispatch_semaphore_signal(sem); // ⑤
    });
    
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // ④
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任務(wù)3:%@",[NSThread currentThread]); // ⑥
    });
    
    // 執(zhí)行順序:① - ② - ③ - ④ - ⑤ - ⑥

}

通過控制信號(hào)量通過數(shù),就可實(shí)現(xiàn)鎖的功能酒请。

pthread_mutex(互斥鎖)

  • 阻塞線程并sleep(加鎖)骡技,加鎖過程中切換上下(主動(dòng)出讓時(shí)間片,線程休眠羞反,等待下一次喚醒)布朦、cpu的搶占、信號(hào)的發(fā)送等開銷昼窗。
  • 如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了是趴,線程會(huì)進(jìn)入休眠狀態(tài)等待鎖。一旦被訪問的資源被解鎖澄惊,則等待資源的線程會(huì)被喚醒唆途。
  • 互斥鎖范圍,應(yīng)該盡量械肛搬;鎖定范圍越大,效率越差毕贼。
  • 能夠給任意NSObject對(duì)象加鎖温赔。

加解鎖流程:sleep(加鎖) -> 出讓時(shí)間片 -> 線程休眠 -> 等待喚醒 -> running(解鎖)

時(shí)間?(quantum):系統(tǒng)給每個(gè)正在運(yùn)行的進(jìn)程或線程微觀上的一段CPU時(shí)間。

//  導(dǎo)入互斥鎖頭文件--C語(yǔ)言
#import <pthread.h>
// 可添加成員變量
 pthread_mutex_t mutex;

- (void)myfun
{
    pthread_mutex_init(&mutex, NULL);
    
}
- (void)MyLockingFunction
{
    pthread_mutex_lock(&mutex);
    // Do something.
    pthread_mutex_unlock(&mutex);
}
- (void)dealloc
{  
     // 不用要釋放掉
    pthread_mutex_destroy(&mutex);
}
// 這只是簡(jiǎn)單使用鬼癣,具體還需針對(duì)進(jìn)行錯(cuò)誤代碼處理
互斥鎖 vs 自旋鎖

相同:都能保證同一時(shí)間只有一個(gè)線程訪問共享資源陶贼。都能保證線程安全啤贩。
不同:

  • 互斥鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了,線程會(huì)進(jìn)入休眠狀態(tài)等待鎖拜秧。一旦被訪問的資源被解鎖痹屹,則等待資源的線程會(huì)被喚醒。
  • 自旋鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了枉氮,線程會(huì)以忙等的方式等待鎖志衍,一旦被訪問的資源被解鎖,則等待資源的線程會(huì)立即執(zhí)行嘲恍。

NSLock(互斥鎖)

NSLock是對(duì)底層pthread_mutex的封裝足画。一般使用有:

self.lock = [[NSLock alloc] init];
[self.lock tryLock]; // 嘗試加鎖雄驹;返回YES or NO
[self.lock lock]; // 加鎖
[self.lock unlock]; // 解鎖

底層原理

NSLock - Swift Foundation

NSLockFoundation下的佃牛,閉源則源碼不可見。借助Swift的Foundation可以看看同集成NSLockingNSLock在底層做了什么操作医舆。

    1. 調(diào)用必須初始化俘侠;而底層則直接調(diào)用了互斥鎖pthread_mutex_init.(可以知道性能相近的原因了)
    1. 底層實(shí)現(xiàn)也是調(diào)用了pthread_mutexlockunlock.即就是對(duì)pthread_mutex的封裝。

在Apple官方文檔中指出

Warning
The NSLock class uses POSIX threads to implement its locking behavior. When sending an
unlock message to an NSLock object, you must be sure that message is sent from the
same thread that sent the initial lock message. Unlocking a lock from a different thread can result in undefined behavior.
Tra:本NSLock類使用POSIX線程執(zhí)行其鎖定行為蔬将。向NSLock對(duì)象發(fā)送解鎖消息時(shí)爷速,必須確保該消
息是從發(fā)送初始鎖定消息的同一線程發(fā)送的。從其他線程解鎖鎖可能導(dǎo)致未定義的行為霞怀。

所有它僅限用于同一線程中惫东,且也不應(yīng)使用此類來實(shí)現(xiàn)遞歸鎖。lock在同一線程上兩次調(diào)用該方法將永久鎖定您的線程毙石。原因是加鎖還未解鎖又再一次加鎖廉沮,一直在加鎖就會(huì)陷入死鎖狀態(tài)。如下:

NSLock *lock = [[NSLock alloc] init];
for (int i= 0; i<50; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        static void (^testMethod)(int);
        testMethod = ^(int value){
            [lock lock];
            if (value > 0) {
              NSLog(@"current value = %d",value);
              testMethod(value - 1);
            }
        };
        testMethod(10);
        [lock unlock];
    });
}  

可以使用NSRecursiveLock來實(shí)現(xiàn)遞歸鎖徐矩,也可使用@synchronized替代處理滞时。

@synchronized

@synchronized是開發(fā)中用的非常廣泛的一種鎖,目的就是防止不同的線程同時(shí)執(zhí)行同一段代碼滤灯。但是就其性能而言坪稽,可謂慘不忍睹。常言存在即合理鳞骤,廣泛使用窒百,面試中常常被提及,就需要探索一下其底層原理豫尽。

首先確定下研究方法:1. 匯編篙梢;2. Clang。

有這樣一個(gè)例子拂募,「onePx」奶茶店生意很好庭猩,奶茶就剩20杯的量窟她,三個(gè)窗口賣(類似三條條線程),這還有越賣越多情況蔼水,就不符合逾期了震糖。這就多線程對(duì)同一資源訪問,發(fā)生了數(shù)據(jù)錯(cuò)亂趴腋。

多個(gè)窗口賣奶茶

當(dāng)然了吊说,主題是鎖,就通過加鎖即可解決优炬。譬如加一個(gè)@synchronized颁井,就完美控制。
image.png

匯編

@synchronized打下一個(gè)端點(diǎn)蠢护,打開匯編雅宾,Xcode菜單欄,Debug -> Debug Workflow -> Always Show Disassembly

匯編斷點(diǎn)-無鎖

匯編斷點(diǎn)-加鎖

通過匯編可以窺見一二葵硕,在加鎖和無鎖的情況下有很大區(qū)別眉抬,執(zhí)行流程變得更加復(fù)雜,且多出兩個(gè)關(guān)鍵方法:objc_sync_enter懈凹、objc_sync_exit蜀变,這就是@synchronized的進(jìn)出口方法。

Clang

在main.m中編寫一個(gè)@synchronized方法

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        @synchronized (appDelegateClassName) {
        }
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

然后對(duì)其Clang指令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp編譯出已C++文件介评,查看底層

main.cpp的main部分底層

由圖中可驗(yàn)證匯編形式下库北,@synchronized的底層會(huì)調(diào)用objc_sync_enter、objc_sync_exit们陆。如果捕獲到異常寒瓦,就把異常拋出。

objc_sync_enter

在之前的工程中下一個(gè)方法為objc_sync_enter的符號(hào)斷點(diǎn)棒掠,我們可以知道底層源碼是在libobjc.A.dylib中孵构。也可以在@synchronized處下斷點(diǎn),進(jìn)入?yún)R編然后調(diào)試到objc_sync_enter烟很,點(diǎn)擊底部調(diào)試菜單欄step into進(jìn)入objc_sync_enter的深一層匯編颈墅,也可以知道其歸屬于libobjc.A.dylib范疇。

libobjc.A.dylib -> objc_sync_enter

其實(shí)從這里就可以知道它在底層大致做了什么雾袱,做一個(gè)等值判斷恤筛,根據(jù)判斷結(jié)果,它會(huì)調(diào)用id2Data的一個(gè)方法芹橡,然后再會(huì)調(diào)用一個(gè)os_unfair_recursive_lock_lock_with_options毒坛;否則就跳轉(zhuǎn)調(diào)試超父類,調(diào)用一個(gè)方法objc_sync_nil。嚴(yán)謹(jǐn)一點(diǎn)還是要走底層代碼摸索一波煎殷。

打開一份前面文章分析用過的objc源碼屯伞,我們可以查找到對(duì)應(yīng)的源碼

// Begin synchronizing on 'obj'.  開始同步
// Allocates recursive mutex associated with 'obj' if needed.
// 如果需要,分配與“ obj”關(guān)聯(lián)的遞歸互斥體豪直。 這里可以知道劣摇,它是一把遞歸互斥鎖,具體看底層弓乙。
// Returns OBJC_SYNC_SUCCESS once lock is acquired. 
// 成功時(shí)返回一個(gè) OBJC_SYNC_SUCCESS
int objc_sync_enter(id obj)
{
    int result = OBJC_SYNC_SUCCESS;
    // 先判空
    if (obj) { 
        // id2data -> 關(guān)鍵方法末融。obj 不為空,從id2Data 獲取一個(gè)SyncData類型數(shù)據(jù)暇韧,然后加鎖勾习。
        SyncData* data = id2data(obj, ACQUIRE); 
        ASSERT(data);
        // 加鎖
        /**
         mutex 類型為 recursive_mutex_t;
        */
        data->mutex.lock();
    } else {
        // @synchronized(nil) does nothing 如果加鎖傳入的obj為空懈玻,什么也不做
        // 如果obj 為空巧婶,報(bào)以奔潰  objc_sync_nil()
        if (DebugNilSync) {
            _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
        } 
        objc_sync_nil();
    }
    return result;
}
  • SyncData結(jié)構(gòu) + SyncList結(jié)構(gòu)
typedef struct alignas(CacheLineSize) SyncData {
    struct SyncData* nextData; // SyncData -> SyncData ,不斷的鏈接。這證明是一個(gè)鏈表結(jié)構(gòu)
    DisguisedPtr<objc_object> object; //通過運(yùn)算使指針隱藏于系統(tǒng)工具酪刀,同時(shí)保持指針的能力粹舵,其作用是通過計(jì)算把保存的 T 的指針隱藏起來,實(shí)現(xiàn)指針到整數(shù)的映射骂倘。
    int32_t threadCount;  // number of THREADS using this block。該代碼塊的線程數(shù)
    recursive_mutex_t mutex; // 證明其底部就是遞歸互斥
} SyncData;

struct SyncList {
    SyncData *data;
    spinlock_t lock;

    constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};
// Use multiple parallel lists to decrease contention among unrelated objects.使用多個(gè)并行列表以減少不相關(guān)對(duì)象之間的爭(zhēng)用巴席。
// 哈希出對(duì)象的數(shù)組下標(biāo)历涝,然后取出數(shù)組對(duì)應(yīng)元素的 lock 或 data
#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap<SyncList> sDataLists;

SyncData做為一個(gè)一個(gè)的節(jié)點(diǎn),依次存儲(chǔ)漾唉,每個(gè) SyncList 結(jié)構(gòu)體都有個(gè)指向 SyncData 節(jié)點(diǎn)鏈表頭部的指針荧库,也有一個(gè)用于防止多個(gè)線程對(duì)此列表做并發(fā)修改的鎖。類似SyncData -> SyncData -> SyncData ...赵刑,它是鏈表形式存儲(chǔ)分衫,不同的并行列表間互相不對(duì)數(shù)據(jù)進(jìn)行訪問。
鏈表的底層是通過哈希算法來存儲(chǔ)的般此,即StripedMap底層就是是將對(duì)象指針在內(nèi)存的地址轉(zhuǎn)化為無符號(hào)整型,通過算法((addr >> 4) ^ (addr >> 9)) % StripeCount, 來獲取下標(biāo)indexForPointer蚪战。

StripedMap

SyncList示意圖

SyncData單向存儲(chǔ)鏈表
  • obj == nil時(shí)啥也不做


    obj = nil --> do nothing

無論是objc_sync_enter還是objc_sync_exit 都調(diào)用了一個(gè)關(guān)鍵方法id2Data(),則我們的側(cè)重點(diǎn)就是在里面的實(shí)現(xiàn)铐懊。

id2Data

宏觀查看id2Data
  • 第一個(gè)情況 : SUPPORT_DIRECT_THREAD_KEYS = 1邀桑;即為快速緩存查找
    線程緩存池中快速查找
  1. 在線程緩存池中進(jìn)行快速查找對(duì)象,獲取鎖的數(shù)量科乎,再做一些異常判斷壁畸;
  2. 判斷why,如果類型為ACQUIRE,則使得lockCount 加1,再存儲(chǔ)下來捏萍;
    如果類型為RELEASE太抓,則使得lockCount 減1,再存儲(chǔ)下來令杈;
    如果類型為CHECK,啥也不做腻异。
  • 第二個(gè)情況:SyncCache緩存查找
    SyncCache緩存查找

SyncCache結(jié)構(gòu),與tls線程緩存相似这揣,它也是鏈?zhǔn)酱鎯?chǔ)悔常。

typedef struct {
    SyncData *data;
    unsigned int lockCount;  // number of times THIS THREAD locked this block 線程加鎖次數(shù)
} SyncCacheItem;

typedef struct SyncCache {
    unsigned int allocated;
    unsigned int used;
    SyncCacheItem list[0]; 
} SyncCache;

有所不同的是,它如果有多條線程時(shí)给赞,就會(huì)有多個(gè)這樣的list机打。


SyncCacheItem存儲(chǔ)SyncData

這個(gè)情況下就是在SyncCache的緩存列表里,不同線程間片迅,查找是否有匹配對(duì)象残邀,如果找到匹配對(duì)應(yīng)的類型,進(jìn)行lockCount加和減操作柑蛇。

  • 第一次進(jìn)來
    首次進(jìn)入鎖模塊

    先在線程池中遍歷獲取p->nextData,通過 p =*listp取出第一個(gè)SyncData數(shù)據(jù), 判斷p->object 是否為傳入object, 如果相等芥挣,則存儲(chǔ)下object的持有者SyncDatap;跳轉(zhuǎn)到done耻台,通過判斷fastCacheOccupied, YES時(shí)存儲(chǔ)到快速線程緩存中空免;否則存儲(chǔ)到線程緩存(SyncCache)中。

【總結(jié)】

  • 首次進(jìn)來: 沒有鎖盆耽,threadCount = 1蹋砚,lockCount = 1, 存到tls_set_direct中摄杂;
  • 不是第一次進(jìn)來坝咐,是在tls鏈表進(jìn)行快速緩存查找的,它們是在同一個(gè)線程進(jìn)行lockCount加析恢,并且將result存到tls_set_direct;
  • 不是第一次但是在SyncCache中查找的墨坚,則可能在一個(gè)線程或多個(gè)線程中遍歷SyncCacheItem類型的list單向鏈表,查找SyncData映挂,查找到也會(huì)對(duì)lockCount加操作泽篮。

objc_sync_exit

// End synchronizing on 'obj'. 
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{
    int result = OBJC_SYNC_SUCCESS;
    
    if (obj) {
        SyncData* data = id2data(obj, RELEASE); 
        if (!data) {
            result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
        } else {
            bool okay = data->mutex.tryUnlock();
            if (!okay) {
                result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
            }
        }
    } else {
        // @synchronized(nil) does nothing
    }
    return result;
}

這解鎖就是反向操作了,這可不是像某些人反向吸Y那樣子袖肥。??
它要判斷obj咪辱,然后在id2Data里面做release操作,使得lockCount減少椎组;
如果拿到的非空data閑嘗試解鎖油狂,解鎖成功的即返回result;解鎖失敗的,及時(shí)報(bào)錯(cuò)专筷。

【@synchnized 坑點(diǎn)】
坑點(diǎn)1:經(jīng)過以上的分析弱贼,@synchnized慢的原因就非常清楚了,它的加鎖解鎖都經(jīng)過了一些列的增刪改查再加緩存磷蛹,鏈表接口的存取都會(huì)影響速度吮旅。
坑點(diǎn)2:也是一個(gè)面試題

- (void)testSynchronized {
    for (int i = 0; i < 10000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
                self.dataSources = [NSMutableArray array];
        });
    }
}

執(zhí)行之后會(huì)卡死∥犊龋可以通過開啟

打開Zombie Objects(僵尸對(duì)象)

再次運(yùn)行就可以看到奔潰庇勃。原因是在反復(fù)初始化,調(diào)用setter retain 新值槽驶,釋放舊值责嚷。線程不斷的release舊值,導(dǎo)致了野指針掂铐。
嘗試鎖一下

- (void)testSynchronized {
    for (int i = 0; i < 10000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
             @synchronized (self.dataSources) { 
                self.dataSources = [NSMutableArray array];
            }
        });
    }
}

但是會(huì)出現(xiàn)同樣的奔潰罕拂,原因是:self.dataSources的生命周期,它是會(huì)被釋放的全陨,釋放后為nil爆班,在前面分析中就可以知道,如果加鎖對(duì)象為nil辱姨,則在「鎖」內(nèi)就do nothing柿菩。

所以@synchnized一般鎖self,保證鎖住的objc的生命周期未結(jié)束。但亦不能一直鎖self炮叶,在底層objc_sync_enter的時(shí)候碗旅,self的鏈表會(huì)很多,就會(huì)導(dǎo)致鏈表查詢很繁瑣镜悉,性能降低更加明顯。

補(bǔ)充:atomic & nonatomic

atomic

  • atomic 原?屬性(線程安全)医瘫,針對(duì)多線程設(shè)計(jì)的侣肄,需要消耗?量的資源
  • atomic 本身就有?把鎖(?旋鎖)
  • 保證同?時(shí)間只有?個(gè)線程能夠?qū)?,但是同?個(gè)時(shí)間多個(gè)線程都可以取值醇份。(單寫多讀:?jiǎn)蝹€(gè)線程寫?稼锅,多個(gè)線程可以讀取)

nonatomic

  • nonatomic ?原?屬性
  • nonatomic:?線程安全僚纷,適合內(nèi)存?的移動(dòng)設(shè)備矩距。

屬性應(yīng)都聲明為 nonatomic
盡量避免多線程搶奪同?塊資源怖竭;
盡量將加鎖锥债、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減?移動(dòng)客戶端的壓?。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末哮肚,一起剝皮案震驚了整個(gè)濱河市登夫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌允趟,老刑警劉巖恼策,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異潮剪,居然都是意外死亡涣楷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門抗碰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狮斗,“玉大人,你說我怎么就攤上這事改含∏榱洌” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵捍壤,是天一觀的道長(zhǎng)骤视。 經(jīng)常有香客問我,道長(zhǎng)鹃觉,這世上最難降的妖魔是什么专酗? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮盗扇,結(jié)果婚禮上祷肯,老公的妹妹穿的比我還像新娘。我一直安慰自己疗隶,他們只是感情好佑笋,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著斑鼻,像睡著了一般蒋纬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坚弱,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天蜀备,我揣著相機(jī)與錄音,去河邊找鬼荒叶。 笑死碾阁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的些楣。 我是一名探鬼主播脂凶,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼宪睹,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了艰猬?” 一聲冷哼從身側(cè)響起横堡,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎冠桃,沒想到半個(gè)月后命贴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡食听,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年胸蛛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了狗准。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蔼囊。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡放接,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出迈喉,到底是詐尸還是另有隱情放航,我是刑警寧澤盗飒,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站逆趣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宣渗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一痕囱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鞍恢,春花似錦涯鲁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)岛请。三九已至旭寿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間崇败,已是汗流浹背盅称。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工肩祥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人缩膝。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓混狠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親疾层。 傳聞我的和親對(duì)象是個(gè)殘疾皇子将饺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容