316,iOS的線程鎖的詳解

之前講過鎖有兩種形式:
一種是忙等就像OSSpinLock這種自旋鎖,
一種是讓線程睡眠摊趾。os_unfair_lock就是讓線程睡眠币狠,所以它避免了自旋鎖導致的優(yōu)先級反轉問題

在iOS開發(fā)中,不可避免的需要使用到多線程砾层。但是使用多線程的過程中漩绵,如果使用不當,就會造成數(shù)據(jù)混亂梢为,那要怎么保證多線程使用中不會因為訪問同一個內存空間而造成數(shù)據(jù)混亂呢渐行?這個時候鎖(LOCK)就該閃亮登場了轰坊。本文會從以下幾個方面介紹鎖铸董,希望對大家有幫助:

一、 鎖是什么以及為什么需要肴沫?

1)鎖是什么以及為什么需要粟害?
2)iOS中都有哪些鎖?
3)鎖的使用颤芬?

鎖是一種保證多線程并發(fā)執(zhí)行安全的方式悲幅,避免 同一時間,多個線程同時讀取并修改一個值而產 生混亂站蝠。

先來直觀感受一下多線程不加鎖的混亂汰具,我們以火車站賣票為例,假設有20張票菱魔,同時有兩個窗口售票:

- (void)ticketTest{
    self.ticketsCount = 20;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (NSInteger i = 0; i < 2; i++) {
        dispatch_async(queue, ^{
            for (int i = 0; i < 10; i++) {
                [self sellingTickets];//多線程售票
            }
        });
    }
}

- (void)sellingTickets{
    NSInteger oldMoney = self.ticketsCount;
    sleep(.2);
    oldMoney -= 1;
    self.ticketsCount = oldMoney;
    NSLog(@"當前剩余票數(shù)-> %ld", oldMoney);
}

此時得到的結果是這樣的

2019-01-08 15:08:55.954142+0800 LockDemo[19360:817602] 當前剩余票數(shù)-> 19
2019-01-08 15:08:55.954142+0800 LockDemo[19360:817605] 當前剩余票數(shù)-> 19
2019-01-08 15:08:55.954349+0800 LockDemo[19360:817605] 當前剩余票數(shù)-> 18
2019-01-08 15:08:55.954349+0800 LockDemo[19360:817602] 當前剩余票數(shù)-> 18
2019-01-08 15:08:55.954460+0800 LockDemo[19360:817605] 當前剩余票數(shù)-> 17
2019-01-08 15:08:55.954460+0800 LockDemo[19360:817602] 當前剩余票數(shù)-> 16
2019-01-08 15:08:55.954543+0800 LockDemo[19360:817605] 當前剩余票數(shù)-> 15
2019-01-08 15:08:55.954622+0800 LockDemo[19360:817602] 當前剩余票數(shù)-> 14
2019-01-08 15:08:55.954674+0800 LockDemo[19360:817605] 當前剩余票數(shù)-> 13
2019-01-08 15:08:55.955669+0800 LockDemo[19360:817602] 當前剩余票數(shù)-> 12
2019-01-08 15:08:55.956128+0800 LockDemo[19360:817605] 當前剩余票數(shù)-> 11
2019-01-08 15:08:55.956876+0800 LockDemo[19360:817602] 當前剩余票數(shù)-> 10
2019-01-08 15:08:55.957357+0800 LockDemo[19360:817605] 當前剩余票數(shù)-> 9
2019-01-08 15:08:55.957550+0800 LockDemo[19360:817602] 當前剩余票數(shù)-> 8
2019-01-08 15:08:55.957724+0800 LockDemo[19360:817605] 當前剩余票數(shù)-> 7
2019-01-08 15:08:55.957901+0800 LockDemo[19360:817602] 當前剩余票數(shù)-> 6
2019-01-08 15:08:55.958065+0800 LockDemo[19360:817605] 當前剩余票數(shù)-> 5
2019-01-08 15:08:55.958221+0800 LockDemo[19360:817602] 當前剩余票數(shù)-> 4
2019-01-08 15:08:55.958391+0800 LockDemo[19360:817605] 當前剩余票數(shù)-> 3
2019-01-08 15:08:55.958565+0800 LockDemo[19360:817602] 當前剩余票數(shù)-> 2

不加鎖我們看到很明顯的發(fā)生了混亂留荔。我們對鎖的需要也就不言而喻了。

二澜倦、 iOS中都有哪些鎖聚蝶?

從大的方向講有兩種鎖:互斥鎖、自旋鎖藻治。這兩種類型下分別有自己對應的鎖

image.png

互斥鎖和自旋鎖的對比:
這兩種鎖的相同點不必多說碘勉,都可以避免多線程訪問同一個值發(fā)生混亂,重點說一下兩種的不同點:

互斥鎖:如果共享數(shù)據(jù)已經有其他線程加鎖了桩卵,線程會進入休眠狀態(tài)等待鎖验靡。一旦被訪問的資源被解鎖倍宾, 則等待資源的線程會被喚醒

自旋鎖:如果共享數(shù)據(jù)已經有其他線程加鎖了,線程會以死循環(huán)的方式等待鎖胜嗓,一旦被訪問的資源被解鎖凿宾, 則等待資源的線程會立即執(zhí)行

自旋鎖的特點:

1.自旋鎖的性能高于互斥鎖,因為響應速度快
2.自旋鎖雖然會一直自旋等待獲取鎖兼蕊,但不會一直占用CPU初厚,超過了操作系統(tǒng)分配的時間片會被強制掛起
3.自旋鎖如果不能保證所有線程都是同一優(yōu)先級,則可能造成死鎖孙技。

因為以上的特點产禾,自旋鎖和互斥鎖也有不同的使用場景:

多核處理器情況下: 如果預計線程等待鎖的時間比較短,短到比線程兩次切換上下文的時間還要少的情況下牵啦,自旋鎖是更好的選擇亚情。
如果時間比較長,則互斥鎖是比較好的選擇哈雏。 單核處理器情況下: 不建議使用自旋鎖楞件。

dispatch_semaphore

dispatch_semaphore 是信號量,但當信號總量設為 1 時也可以當作鎖來裳瘪。在沒有等待情況出現(xiàn)時土浸, 它的性能比pthread_mutex 還要高,但一旦有等待情況出現(xiàn)時彭羹,性能就會下降許多黄伊。相對于 OSSpinLock 來說,它的優(yōu)勢在于等待時不會消耗 CPU 資源派殷。

使用也是非常的簡單又方便还最,會用下面幾個函數(shù)就可以:

dispatch_semaphore_create(long value),//創(chuàng)建鎖
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)//接受信號毡惜,信號量加1
dispatch_semaphore_signal(dispatch_semaphore_t dsema)拓轻,//發(fā)送信號,信號量減1
 -(void) dispatch_semaphoreDemo
{
    // 如果信號量的值 > 0经伙,就讓信號量的值減1扶叉,然后繼續(xù)往下執(zhí)行代碼
    // 如果信號量的值 <= 0,就會休眠等待橱乱,直到信號量的值變成>0辜梳,就讓信號量的值減1,然后繼續(xù)往下執(zhí)行代碼
    // 如果等待期間沒有獲取到信號量或者信號量的值一直為0泳叠,那么等到timeout時作瞄,其所處線程自動執(zhí)行其后語句。
    // dispatch_time_t  t = dispatch_time(DISPATCH_TIME_NOW, 1*1000*1000); 1ms
    // DISPATCH_TIME_NOW沒有超時時間 立即執(zhí)行后面的代碼  DISPATCH_TIME_FOREVER一直等待 直到獲得信號量才執(zhí)行后面的代碼

    dispatch_semaphore_t signal = dispatch_semaphore_create(1); //傳入值必須 >=0, 若傳入為0則阻塞線程并等待timeout,時間到后會執(zhí)行其后的語句
    dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);
    //線程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"線程1 等待ing");
        dispatch_semaphore_wait(signal, overTime); //signal 值 -1
        NSLog(@"執(zhí)行線程1");
        dispatch_semaphore_signal(signal); //signal 值 +1
        NSLog(@"線程1 發(fā)送信號");
    });
    
    //線程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//        sleep(1);
        NSLog(@"線程2 等待ing");
        dispatch_semaphore_wait(signal, overTime);
        NSLog(@"執(zhí)行線程2");
        dispatch_semaphore_signal(signal);
        NSLog(@"線程2 發(fā)送信號");
    });
}

初始創(chuàng)建了一個信號量為1的signal危纫,線程1和線程2的執(zhí)行順序不一定宗挥,但不管哪個先執(zhí)行乌庶,另一個都不會再去搶占,因為此時的信號量為0契耿,直到當前線程執(zhí)行完成瞒大,發(fā)送一個新的信號,信號量為1搪桂,此時這個等待線程才能繼續(xù)執(zhí)行透敌。

4.pthread_mutex

pthread_mutex是c底層的線程鎖,關于pthread的各種同步機制踢械,感興趣的可以看看這篇文章pthread的各種同步機制酗电,可謂講的相當全面了

/*
 PTHREAD_MUTEX_NORMAL 缺省類型,也就是普通鎖内列。當一個線程加鎖以后撵术,其余請求鎖的線程將形成一個等待隊列,并在解鎖后先進先出原則獲得鎖话瞧。
 PTHREAD_MUTEX_ERRORCHECK 檢錯鎖嫩与,如果同一個線程請求同一個鎖,則返回 EDEADLK交排,否則與普通鎖類型動作相同划滋。這樣就保證當不允許多次加鎖時不會出現(xiàn)嵌套情況下的死鎖。
 PTHREAD_MUTEX_RECURSIVE 遞歸鎖个粱,允許同一個線程對同一個鎖成功獲得多次古毛,并通過多次 unlock 解鎖翻翩。
 PTHREAD_MUTEX_DEFAULT 適應鎖都许,動作最簡單的鎖類型,僅等待解鎖后重新競爭嫂冻,沒有等待隊列胶征。
 */
- (void)pthreadmutexDemo
{
    //定義pthreadmutex鎖的屬性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);//定義鎖的屬性PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK桨仿、PTHREAD_MUTEX_RECURSIVE
    
    //創(chuàng)建鎖
    static pthread_mutex_t pLock;
    pthread_mutex_init(&pLock, &attr);
    
    //static pthread_mutex_t pLock = PTHREAD_MUTEX_INITIALIZER;//另一種創(chuàng)建鎖的方式睛低,不需要設置屬性的時候

    //1.線程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"線程1 準備上鎖");
        pthread_mutex_lock(&pLock);
        sleep(3);
        NSLog(@"執(zhí)行線程1");
        pthread_mutex_unlock(&pLock);
    });

    //2.線程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"線程2 準備上鎖");
        pthread_mutex_lock(&pLock);
        NSLog(@"執(zhí)行線程2");
        pthread_mutex_unlock(&pLock);
    });
    
    //3.線程3  測試遞歸鎖   需要定義鎖的屬性為PTHREAD_MUTEX_RECURSIVE
    NSMutableArray *arrar = [NSMutableArray array];
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^RecursiveBlock)(int);
        RecursiveBlock = ^(int value) {
            pthread_mutex_lock(&pLock);
            if (value > 0) {
                NSLog(@"value: %d %@", value,arrar);
                [arrar addObject:@(value)];
                RecursiveBlock(value - 1);
            }
        };
        RecursiveBlock(4);
        
    //NSRecursiveLock基于PTHREAD_MUTEX_RECURSIVE的pthread_mutex_lock封裝
}

5.NSLock、NSCondition服傍、NSConditionLock钱雷、NSRecursiveLock

以上所列四種鎖全部是基于pthread_mutex封裝的面向對象的鎖。這幾種鎖都遵守NSCopying協(xié)議吹零,此協(xié)議中提供了加鎖和解鎖方法罩抗。

@protocol NSLocking

- (void)lock;
- (void)unlock;

@end

NSLock的使用,非常簡單

- (void)NSLockDemo
{
    // 初始化鎖
    NSLock *lock = [[NSLock alloc] init];
    //線程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"線程1 嘗試加鎖ing...");
        [lock lock]; // 加鎖灿椅,也有tryLock方法
        sleep(3);//睡眠3秒
        NSLog(@"執(zhí)行線程1");
        [lock unlock];// 解鎖
        NSLog(@"線程1解鎖成功");
    });
    
    //線程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"線程2 嘗試加鎖ing...");
        BOOL x =  [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];//超時時間之前不能獲得鎖就返回NO
        if (x) {
            NSLog(@"執(zhí)行線程2");
            [lock unlock];
            NSLog(@"線程2解鎖成功");
        }else{
            NSLog(@"線程2加鎖失敗");
        }
    });
}

這里NSCodition和NSConditionLock是比較有意思的兩個鎖套蒂,首先看一下NSCondition钞支,它的使用和dispatch_semaphore有異曲同工之妙。NSCondition提供以下幾個方法:

- (void)wait;   // 線程等待
- (BOOL)waitUntilDate:(NSDate *)limit;  // 設置線程等待時間操刀,過了這個時間就會自動執(zhí)行后面的代碼
- (void)signal; // 喚醒一個設置為wait等待的線程
- (void)broadcast;  // 喚醒所有設置為wait等待的線程

前兩個是使線程等待烁挟,后兩個是喚醒等待的線程。

- (void)NSConditionDemo
{
   //比如刪除數(shù)組中的所有元素骨坑,等數(shù)組有元素時再去刪除
    NSCondition *lock = [[NSCondition alloc] init];
    NSMutableArray *array = [[NSMutableArray alloc] init];
    //線程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lock];
        while (!array.count) {
            [lock wait];
        }
        [array removeAllObjects];
        NSLog(@"array removeAllObjects");
        [lock unlock];
    });

    //線程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);//以保證讓線程2的代碼后執(zhí)行
        [lock lock];
        [array addObject:@1];
        NSLog(@"array addObject:@1");
        [lock signal];
        [lock unlock];
    });
}

NSConditionLock提供以下幾個方法撼嗓,相比其他鎖多了condition,也就是傳說中的條件鎖:

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

使用很簡單

- (void)NSConditionLockDemo
{
    NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];
    //線程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if([cLock tryLockWhenCondition:0]){
            NSLog(@"線程1");
            [cLock unlockWithCondition:1];
        }else{
            NSLog(@"失敗");
        }
    });
    
    //線程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lockWhenCondition:3];
        NSLog(@"線程2");
        [cLock unlockWithCondition:2];
    });
    
    //線程3
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lockWhenCondition:1];
        NSLog(@"線程3");
        [cLock unlockWithCondition:3];
    });
}

初始化NSConditionLock時需要指定當前condition的值欢唾,比如上面例子中初始化的condition設置為0静稻,三個線程加鎖時會匹配這個condition,只有是0的才能加鎖成功匈辱。解鎖時也需要給定一個condition振湾,可以自己隨便定義,然后繼續(xù)尋找匹配condition的線程加鎖…依此類推直到加解鎖完所有任務亡脸。根據(jù)NSConditionLock的這種特性押搪,可以用來做多線程任務之間的依賴。

6.@synchronized

@synchronized可能是我們平常用的比較多的加鎖方式浅碾,為什么呢大州?因為它使用起來最簡單。

- (void)synchronizedDemo
{
    @synchronized (self) {
        [self sellingTickets];
    }
}

你可以給任何 Objective-C 對象上加個 @synchronized垂谢。那么我們也可以在上面的例子中用 @synchronized(self.view) 來替代 @synchronized(self)厦画,效果是相同的。
對這種鎖內部實現(xiàn)原理感興趣的滥朱,可以看看這篇文章關于@synchronized根暑,這兒比你想知道的還要多
那這么簡單的語法下系統(tǒng)為我們做了什么呢?我們把這段代碼翻譯成匯編代碼(xcode可以自動翻譯徙邻,在Product–Perform Action–Assemble “文件名”排嫌,可以把仍任意oc代碼翻譯成匯編代碼),看到
@synchronized (self) {
}
實際上是調用了_objc_sync_enter和_objc_sync_exit這一對方法

image.png

我們可以在objc源碼中查找到這兩個方法的實現(xiàn)缰犁,具體的原理就不展開了淳地,有興趣看看上面的文章吧。
下面是對上面介紹的各種鎖的執(zhí)行效率的定性分析(只代表加解鎖的效率帅容,比如執(zhí)行1萬次加解鎖的操作耗費的時間)

image.png

如上颇象,就是本次介紹的iOS鎖的知識。最后以一個小小的總結完成鎖的介紹:
1并徘、所有的鎖基本都是創(chuàng)建鎖遣钳、加鎖、等待饮亏、解鎖的流程耍贾,所以并不復雜阅爽。
2.如果追求鎖的極致性能,可以考慮更偏底層實現(xiàn)的pthread_mutex互斥鎖以及信號量的方式荐开。
3.@synchronized的效率最低付翁,但是它使用最方便,所以如果沒有性能瓶頸的話使用它也不錯晃听。

二百侧,iOS的OSSpinLock與os_unfair_lock

首先說說自旋鎖OSSpinLock,它的原理其實就是一個死循環(huán)能扒。

//偽代碼
bool lock = false; // 一開始沒有鎖上佣渴,任何線程都可以申請鎖
do {
    while(lock); // 如果 lock 為 true 就一直死循環(huán),相當于申請鎖
    lock = true; // 掛上鎖初斑,這樣別的線程就無法獲得鎖
    Critical section  // 臨界區(qū)
    lock = false; // 相當于釋放鎖,這樣別的線程可以進入臨界區(qū)
    Reminder section // 不需要鎖保護的代碼
}

但不幸的是OSSpinLock已經在iOS10被蘋果棄用见秤,因為它存在優(yōu)先級反轉的問題砂竖,故不再細講,只需要知道其大概的原理就OK鹃答。

優(yōu)先級反轉:
發(fā)生在低優(yōu)先級線程拿到鎖時乎澄,高優(yōu)先級線程進入忙等(busy-wait)狀態(tài),消耗大量 CPU 時間测摔,
從而導致低優(yōu)先級線程拿不到 CPU 時間置济,也就無法完成任務并釋放鎖。

那為什么忙等會導致低優(yōu)先級線程拿不到時間片锋八?

現(xiàn)代操作系統(tǒng)在管理普通線程時浙于,通常采用時間片輪轉算法(Round Robin,簡稱 RR)查库。每個線程會
被分配一段時間片(quantum)路媚,通常在 10-100 毫秒左右。當線程用完屬于自己的時間片以后樊销,就會
被操作系統(tǒng)掛起,放入等待隊列中脏款,直到下一次被分配時間片围苫。

  • 用鎖的場景:多條線程存在同時操作(刪、查撤师、讀剂府、寫)同一個文件or對象or變量。如果不是同時或者不是同一個那就不用加鎖了剃盾。

  • 介紹:OSSpinLock是在iOS10前還算比較常見的一鐘鎖腺占,其是"忙等"的鎖淤袜,所以適用于輕量級的操作,比如基本數(shù)據(jù)類型的加減衰伯,如int 的-1铡羡,+1操作,“忙等”的鎖,大致的解析就是會一直 while(目標鎖還未釋放)意鲸,然后一直執(zhí)行烦周,所以會很耗cpu的性能

  • 還有另外一種鎖的實現(xiàn),是將線程狀態(tài)改成休眠怎顾,然后等待喚醒读慎。這種其實也不是很省資源,因為線程之間的切換也是非常耗性能的槐雾,大概需要20毫秒的時間夭委。

  • 隱患:會出現(xiàn)優(yōu)先級翻轉的情況.比如線程1優(yōu)先級比較高,線程2優(yōu)先級比較低募强,然后在某一時刻是線程2先獲取到鎖闰靴,所以先是線程2加鎖,這時候钻注,線程1就在while(目標鎖還未釋放)蚂且,這個狀態(tài),但因為線程1優(yōu)先級比較高幅恋,所以系統(tǒng)分配的時間比較多杏死,有可能會沒有分配時間給線程2執(zhí)行后續(xù)的操作(需要做的任務和解鎖)了,這時候就會造成死鎖捆交。

但如果是線程休眠的情況淑翼,在優(yōu)先級高的線程休眠后,優(yōu)先級比較低的線程會給系統(tǒng)調用品追,所以不會有死鎖的情況

  • 使用方法
//導入頭文件
#import <libkern/OSAtomic.h>

//因為多個線程要公用一把鎖玄括,所以定義為屬性,因為是c的肉瓦,所以用assign
@property (assign, nonatomic) OSSpinLock mLock;

//創(chuàng)建
self. mLock = OS_SPINLOCK_INIT;

//加鎖
OSSpinLockLock(&_moneyLock);

// <···你的代碼···>

//解鎖
OSSpinLockUnlock(&_moneyLock);

oc代碼

image.png

這也就是我為什么在前面說iOS10之前自旋鎖比較常見遭京,其實在10之后,系統(tǒng)已經修改了OSSpinLock的實現(xiàn)方式了泞莉,改用了os_unfair_lock 來實現(xiàn)了

os_unfair_lock

介紹:

這是蘋果iOS10之后推出的新的取代OSSpinLock的鎖哪雕。雖然是替代OSSpinLock的,但os_unfair_lock并不是自旋鎖鲫趁,根據(jù)蘋果的官方文檔可以看到其實它是一個互斥鎖斯嚎。
os_unfair_lock使用起來非常簡單,首先需要引入系統(tǒng)庫 #import <os/lock.h>

_unfairLock = OS_UNFAIR_LOCK_INIT;  //唯一的初始化方法
- (void)os_unfair_lockDemo
{
    os_unfair_lock_lock(&_unfairLock);          //加鎖
//    os_unfair_lock_trylock(&_unfairLock);  //嘗試加鎖,加鎖失敗返回NO堡僻,成功返回YES
    [self   sellingTickets];
    os_unfair_lock_unlock(&_unfairLock);    //解鎖
}

os_unfair_lock是在iOS10之后為了替代自旋鎖OSSpinLock而誕生的糠惫,主要是通過線程休眠的方式來繼續(xù)加鎖,而不是一個“忙等”的鎖钉疫。猜測是為了解決自旋鎖的優(yōu)先級反轉的問題硼讽。

OC代碼

image.png

使用:

#import <os/lock.h>

@property (assign, nonatomic) os_unfair_lock mLock;

self.mLock = OS_UNFAIR_LOCK_INIT;

os_unfair_lock_lock(&_mLock);
os_unfair_lock_unlock(&_mLock);

通過查看匯編代碼來看2鐘鎖的不同

注意:要把系統(tǒng)調到10.0以下陌选,我測試自旋鎖的時候調到了9.0
初步代碼
//
//  ViewController.m
//  OSSpinLockAndunfairlock
//
//  Created by LJP on 2020/2/29.
//  Copyright ? 2020 L. All rights reserved.
//

#import "ViewController.h"
#import <libkern/OSAtomic.h>
#import <os/lock.h>

@interface ViewController ()

@property (assign, nonatomic) OSSpinLock mSpinLock;

//@property (assign, nonatomic) os_unfair_lock mUnfairLock;

@property (assign, nonatomic) NSInteger mCount;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.mSpinLock = OS_SPINLOCK_INIT;
    self.mCount = 30;

    NSLog(@"開始");
    [self test];
}

- (void)test {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self subCount];
        }
    });

    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self subCount];
        }
    });

    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self subCount];
        }
    });
}

- (void)subCount {
    sleep(0.5);
    self.mCount = self.mCount - 1;

    NSLog(@"mCount == %ld   name == %@ ", (long)self.mCount, [NSThread currentThread]);
}

@end

運行的結果如下

image.png

從控制圖里可以看出如果不加鎖順序是會亂的理郑,并且有可能最終的結果不是0

加鎖之后
image.png

執(zhí)行幾次后,順序沒有亂

下面就是觀察匯編代碼了

  • 首先把睡眠時間調高一點咨油,然后運行
image.png

可以看到您炉,是選運行線程6 再到線程8 再到線程9

  • 調出顯示匯編指令的面板
image.png
  • si指令 si是讓匯編指令一條一條的向下走
image.png

當你多敲幾次后你會發(fā)現(xiàn),他會一直在這個范圍里走來走去

  • jne指令 其實就是一個判斷語句役电,如果符合條件赚爵,就跳去相應的行數(shù)
image.png

所以說自旋鎖是相當于 while 一直在循環(huán)等待

下面開始測試os_unfair_lock
  • 先把版本調高
  • 然后和自旋一樣的操作
image.png

多次測試之后你會發(fā)現(xiàn) 走到syscall里就會跳會vc的畫面,不會再有下一步的si了。

image.png

猜測是系統(tǒng)調用線程進入休眠了

總結

其實2個鎖都耗性能法瑟,各有優(yōu)劣冀膝,但可能是因為自旋鎖會產生優(yōu)先級反轉,用互斥鎖會比較安全

自旋鎖在 循環(huán)等待的時候會消耗cpu的性能
互斥鎖在 cpu線程調度的時候會消耗cpu性能

所以互斥鎖霎挟,比較適合 臨界代碼 比較耗時間長的 比如 有網絡阻塞 IO 阻塞的情況

自旋鎖窝剖, 因為一直消耗cpu 所以 一般比較適合 臨界代碼比較少的 比較適合段時間操作的 比如 從 mutable 對象里面(dictioanry array hashtable) 讀寫操作的情況

最后的代碼

//
//  ViewController.m
//  OSSpinLockAndunfairlock
//
//  Created by LJP on 2020/2/29.
//  Copyright ? 2020 L. All rights reserved.
//

#import "ViewController.h"
#import <libkern/OSAtomic.h>
#import <os/lock.h>

@interface ViewController ()

//@property (assign, nonatomic) OSSpinLock mSpinLock;

@property (assign, nonatomic) os_unfair_lock mUnfairLock;

@property (assign, nonatomic) NSInteger mCount;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

//    self.mSpinLock = OS_SPINLOCK_INIT;
    self.mUnfairLock = OS_UNFAIR_LOCK_INIT;
    self.mCount = 30;

    NSLog(@"開始");
    [self test];
}

- (void)test {
//    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//
//    dispatch_async(queue, ^{
//        for (int i = 0; i < 10; i++) {
//            [self subCount];
//        }
//    });
//
//    dispatch_async(queue, ^{
//        for (int i = 0; i < 10; i++) {
//            [self subCount];
//        }
//    });
//
//    dispatch_async(queue, ^{
//        for (int i = 0; i < 10; i++) {
//            [self subCount];
//        }
//    });
    
    for (int i = 0; i < 10; i++) {
        [[[NSThread alloc] initWithTarget:self selector:@selector(subCount) object:nil] start];
    }
    
}

- (void)subCount {
    os_unfair_lock_lock(&_mUnfairLock);

    sleep(60);

    self.mCount = self.mCount - 1;

    NSLog(@"mCount == %ld   name == %@ ", (long)self.mCount, [NSThread currentThread]);

    os_unfair_lock_unlock(&_mUnfairLock);
}

@end

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市酥夭,隨后出現(xiàn)的幾起案子赐纱,更是在濱河造成了極大的恐慌,老刑警劉巖熬北,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疙描,死亡現(xiàn)場離奇詭異,居然都是意外死亡讶隐,警方通過查閱死者的電腦和手機起胰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巫延,“玉大人效五,你說我怎么就攤上這事×移溃” “怎么了火俄?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長讲冠。 經常有香客問我,道長适瓦,這世上最難降的妖魔是什么竿开? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任谱仪,我火速辦了婚禮,結果婚禮上否彩,老公的妹妹穿的比我還像新娘疯攒。我一直安慰自己,他們只是感情好列荔,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布敬尺。 她就那樣靜靜地躺著,像睡著了一般贴浙。 火紅的嫁衣襯著肌膚如雪砂吞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天崎溃,我揣著相機與錄音蜻直,去河邊找鬼。 笑死袁串,一個胖子當著我的面吹牛概而,可吹牛的內容都是我干的。 我是一名探鬼主播囱修,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赎瑰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了破镰?” 一聲冷哼從身側響起餐曼,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愧膀,經...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡尼荆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了侧戴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖霸饲,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情臂拓,我是刑警寧澤厚脉,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站胶惰,受9級特大地震影響傻工,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一中捆、第九天 我趴在偏房一處隱蔽的房頂上張望鸯匹。 院中可真熱鬧,春花似錦泄伪、人聲如沸殴蓬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽染厅。三九已至,卻和暖如春津函,著一層夾襖步出監(jiān)牢的瞬間肖粮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工球散, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留尿赚,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓蕉堰,卻偏偏與公主長得像凌净,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子屋讶,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354