iOS中鎖的介紹和使用

在多線程開發(fā)中難免會遇到資源競爭的問題,導(dǎo)致線程不安全問題的發(fā)生网棍,那么怎樣保證同一時間只能有一條線程訪問該資源呢黔龟,首先肯定是在寫代碼時盡可能避免多線程資源競爭的情況發(fā)生,第二就是給可能發(fā)生資源競爭的地方加上鎖滥玷,代碼中常用的鎖有下圖展示的這些氏身,同時每個鎖的性能也有顯示。
Snip20190310_6.png

自旋鎖和互斥鎖

共同點:都能保證同一時刻只能有一個線程操作鎖住的代碼惑畴。都能保證線程安全蛋欣。
不同點:

  • 互斥鎖(mutex):當(dāng)上一個線程的任務(wù)沒有執(zhí)行完畢的時候(被鎖住)如贷,那么下一個線程會進入睡眠狀態(tài)等待任務(wù)執(zhí)行完畢(sleep-waiting)陷虎,當(dāng)上一個線程的任務(wù)執(zhí)行完畢,下一個線程會自動喚醒然后執(zhí)行任務(wù)倒得。
  • 自旋鎖(Spin lock):當(dāng)上一個線程的任務(wù)沒有執(zhí)行完畢的時候(被鎖仔汉臁),那么下一個線程會一直等待(busy-waiting)霞掺,當(dāng)上一個線程的任務(wù)執(zhí)行完畢谊路,下一個線程會立即執(zhí)行。
  • 由于自旋鎖不會引起調(diào)用者睡眠菩彬,所以自旋鎖的效率遠高于互斥鎖
  • 自旋鎖會一直占用CPU缠劝,也可能會造成死鎖

原子操作

nonatomic:非原子屬性,非線程安全骗灶,適合小內(nèi)存移動設(shè)備

atomic:原子屬性惨恭,default,線程安全(內(nèi)部使用自旋鎖)耙旦,消耗大量資源脱羡,單寫多讀,只為setter加鎖,不影響getter方法

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) 
{
   if (offset == 0) {
       object_setClass(self, newValue);
       return;
   }

   id oldValue;
   id *slot = (id*) ((char*)self + offset);

   if (copy) {
       newValue = [newValue copyWithZone:nil];
   } else if (mutableCopy) {
       newValue = [newValue mutableCopyWithZone:nil];
   } else {
       if (*slot == newValue) return;
       newValue = objc_retain(newValue);
   }
if (!atomic) {
       oldValue = *slot;
       *slot = newValue;
   } else {
       spinlock_t& slotlock = PropertyLocks[slot];
       slotlock.lock();
       oldValue = *slot;
       *slot = newValue;        
       slotlock.unlock();
   }

   objc_release(oldValue);
}

void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
   bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
   bool mutableCopy = (shouldCopy == MUTABLE_COPY);
   reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
  1. @synchronized 關(guān)鍵字加鎖
    @synchronized是iOS中最常見的鎖锉罐,也是性能最差的一種帆竹,具體用法如下:
- (void)synchronized {
  NSObject * cjobj = [NSObject new];
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    @synchronized(cjobj){
      NSLog(@"線程1開始");
      sleep(3);
      NSLog(@"線程1結(jié)束");
    }
  });
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(1);
    @synchronized(cjobj){
      NSLog(@"線程2");
    }
  });
}

線程2會等待線程1執(zhí)行完以后才會執(zhí)行,這種鎖在使用時要確保該鎖的唯一標(biāo)識符是相同的才行脓规,即為同一個對象即可栽连,如上述代碼中的cjobj;
優(yōu)點:就是我們不需要在代碼中顯式的創(chuàng)建鎖對象侨舆,便可以實現(xiàn)鎖的機制秒紧,但作為一種預(yù)防措施,@synchronized 塊會隱式的添加一個異常處理例程來保護代碼挨下,該處理例程會在異常拋出的時候自動的釋放互斥鎖熔恢。
缺點:隱式的異常處理例程帶來額外的開銷,造成性能差臭笆。
2绩聘、NSLock(互斥鎖)
iOS中NSLock類的.h文件,從代碼中可以看出耗啦,該類分成了幾個子類:NSLock、NSConditionLock机杜、NSRecursiveLock帜讲、NSCondition,然后有一個 NSLocking 協(xié)議:

@protocol NSLocking
- (void)lock;
- (void)unlock;
@end

雖然 NSLock椒拗、NSConditionLock似将、NSRecursiveLock、NSCondition 都遵循的了 NSLocking 協(xié)議蚀苛,但是它們并不相同在验。
2.1 NSLock 對象鎖
NSLock實現(xiàn)了最基本的鎖,遵循NSLoaking協(xié)議堵未,通過lock和unlock來進行加鎖和解鎖
源碼內(nèi)容:

@interface NSLock : NSObject <NSLocking> {
@private
    void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end

使用方法如下:

- (void)lockTest {
    NSLock *lock = [[NSLock alloc]init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lock];
        NSLog(@"線程1加鎖成功");
        sleep(2);
        [lock unlock];
        NSLog(@"線程1解鎖成功");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lock];
        NSLog(@"線程2加鎖成功");
        sleep(2);
        [lock unlock];
        NSLog(@"線程2解鎖成功");
    });
}

除 lock 和 unlock 方法外腋舌,NSLock 還提供了 tryLock 和 lockBeforeDate:兩個方法,這兩個方法我就不給大家演示了渗蟹,感興趣的可以自己演示下块饺,我把方法對應(yīng)的作用高速大家。
tryLock 并不會阻塞線程雌芽,[cjlock tryLock] 能加鎖返回 YES授艰,不能加鎖返回 NO,然后都會執(zhí)行后續(xù)代碼世落。
這里順便提一下 trylock 和 lock 使用場景:當(dāng)前線程鎖失敗淮腾,也可以繼續(xù)其它任務(wù),用 trylock 合適;當(dāng)前線程只有鎖成功后谷朝,才會做一些有意義的工作洲押,那就 lock,沒必要輪詢 trylock徘禁。以下的鎖都是這樣诅诱。
lockBeforeDate: 方法會在所指定 Date 之前嘗試加鎖,會阻塞線程送朱,如果在指定時間之前都不能加鎖娘荡,則返回 NO,指定時間之前能加鎖驶沼,則返回 YES炮沐。
由于是互斥鎖,當(dāng)一個線程進行訪問的時候回怜,該線程獲得鎖大年,其他線程進行訪問的時候,將被操作系統(tǒng)掛起玉雾,直到該線程釋放鎖翔试,其他線程才能對其進行訪問,從而卻確保了線程安全复旬。但是如果連續(xù)鎖定兩次垦缅,則會造成死鎖問題。

2.2驹碍、 NSRecursiveLock 遞歸鎖
NSRecursiveLock是遞歸鎖壁涎,可以被一個線程多次獲得,而不會引起死鎖志秃。它記錄了成功獲得鎖的次數(shù)怔球,每一次成功的獲得鎖,必須有一個配套的釋放鎖和其對應(yīng)浮还,這樣才不會引起死鎖竟坛。NSRecursiveLock 會記錄上鎖和解鎖的次數(shù)温眉,當(dāng)二者平衡的時候管搪,才會釋放鎖,其它線程才可以上鎖成功左驾。
源碼內(nèi)容:

@private
    void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end

使用方法如下:

- (void)nsrecursivelock{
    NSRecursiveLock * cjlock = [[NSRecursiveLock alloc] init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^RecursiveBlock)(int);
        RecursiveBlock = ^(int value) {
            [cjlock lock];
            NSLog(@"%d加鎖成功",value);
            if (value > 0) {
                NSLog(@"value:%d", value);
                RecursiveBlock(value - 1);
            }
            [cjlock unlock];
            NSLog(@"%d解鎖成功",value);
        };
        RecursiveBlock(3);
    });
}

2019-03-10 16:42:51.487615+0800 AllSuoUser[45654:1840908] 3加鎖成功
2019-03-10 16:42:51.487773+0800 AllSuoUser[45654:1840908] value:3
2019-03-10 16:42:51.487866+0800 AllSuoUser[45654:1840908] 2加鎖成功
2019-03-10 16:42:51.487963+0800 AllSuoUser[45654:1840908] value:2
2019-03-10 16:42:51.488056+0800 AllSuoUser[45654:1840908] 1加鎖成功
2019-03-10 16:42:51.488157+0800 AllSuoUser[45654:1840908] value:1
2019-03-10 16:42:51.488256+0800 AllSuoUser[45654:1840908] 0加鎖成功
2019-03-10 16:42:51.488350+0800 AllSuoUser[45654:1840908] 0解鎖成功
2019-03-10 16:42:51.488468+0800 AllSuoUser[45654:1840908] 1解鎖成功
2019-03-10 16:42:51.488577+0800 AllSuoUser[45654:1840908] 2解鎖成功
2019-03-10 16:42:51.488681+0800 AllSuoUser[45654:1840908] 3解鎖成功
由以上內(nèi)容總結(jié):
如果用 NSLock 的話延刘,cjlock 先鎖上了漫试,但未執(zhí)行解鎖的時候,就會進入遞歸的下一層碘赖,而再次請求上鎖驾荣,阻塞了該線程外构,線程被阻塞了,自然后面的解鎖代碼不會執(zhí)行播掷,而形成了死鎖审编。而 NSRecursiveLock 遞歸鎖就是為了解決這個問題。

2.3歧匈、NSConditionLock
NSConditionLock 對象所定義的互斥鎖可以在使得在某個條件下進行鎖定和解鎖垒酬,它和 NSLock 類似,都遵循 NSLocking 協(xié)議件炉,方法都類似勘究,只是多了一個 condition 屬性,以及每個操作都多了一個關(guān)于 condition 屬性的方法斟冕,例如 tryLock口糕、tryLockWhenCondition:,所以 NSConditionLock 可以稱為條件鎖磕蛇。

  • 只有 condition 參數(shù)與初始化時候的 condition 相等景描,lock 才能正確進行加鎖操作。
  • unlockWithCondition: 并不是當(dāng) condition 符合條件時才解鎖秀撇,而是解鎖之后超棺,修改 condition 的值。
    源碼內(nèi)容:
@interface NSConditionLock : NSObject <NSLocking> {
@private
    void *_priv;
}
- (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;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end

使用方法介紹:

- (void)nsconditionlock {
    NSConditionLock * cjlock = [[NSConditionLock alloc] initWithCondition:0];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cjlock lock];
        NSLog(@"線程1加鎖成功");
        sleep(1);
        [cjlock unlock];
        NSLog(@"線程1解鎖成功");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        [cjlock lockWhenCondition:1];
        NSLog(@"線程2加鎖成功");
        [cjlock unlock];
        NSLog(@"線程2解鎖成功");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(2);
        if ([cjlock tryLockWhenCondition:0]) {
            NSLog(@"線程3加鎖成功");
            sleep(2);
            [cjlock unlockWithCondition:2];
            NSLog(@"線程3解鎖成功");
        } else {
            NSLog(@"線程3嘗試加鎖失敗");
        }
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if ([cjlock lockWhenCondition:2 beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
            NSLog(@"線程4加鎖成功");
            [cjlock unlockWithCondition:1];
            NSLog(@"線程4解鎖成功");
        } else {
            NSLog(@"線程4嘗試加鎖失敗");
        }
    });
}

2017-10-19 15:09:44.010992+0800 Thread-Lock[39230:853946] 線程1加鎖成功
2017-10-19 15:09:45.012045+0800 Thread-Lock[39230:853946] 線程1解鎖成功
2017-10-19 15:09:46.012692+0800 Thread-Lock[39230:853947] 線程3加鎖成功
2017-10-19 15:09:48.016536+0800 Thread-Lock[39230:853947] 線程3解鎖成功
2017-10-19 15:09:48.016564+0800 Thread-Lock[39230:853944] 線程4加鎖成功
2017-10-19 15:09:48.017039+0800 Thread-Lock[39230:853944] 線程4解鎖成功
2017-10-19 15:09:48.017040+0800 Thread-Lock[39230:853945] 線程2加鎖成功
2017-10-19 15:09:48.017215+0800 Thread-Lock[39230:853945] 線程2解鎖成功
由以上內(nèi)容總結(jié):

  1. 在線程 1 解鎖成功之后呵燕,線程 2 并沒有加鎖成功说搅,而是繼續(xù)等了 1 秒之后線程 3 加鎖成功,這是因為線程 2 的加鎖條件不滿足虏等,初始化時候的 condition 參數(shù)為 0,而線程 2
  2. 加鎖條件是 condition 為 1适肠,所以線程 2 加鎖失敗霍衫。
  3. lockWhenCondition 與 lock 方法類似,加鎖失敗會阻塞線程侯养,所以線程 2 會被阻塞著敦跌。
  4. tryLockWhenCondition: 方法就算條件不滿足,也會返回 NO逛揩,不會阻塞當(dāng)前線程柠傍。
  5. lockWhenCondition:beforeDate:方法會在約定的時間內(nèi)一直等待 condition 變?yōu)?2,并阻塞當(dāng)前線程辩稽,直到超時后返回 NO惧笛。
  6. 鎖定和解鎖的調(diào)用可以隨意組合,也就是說 lock逞泄、lockWhenCondition:與unlock患整、unlockWithCondition: 是可以按照自己的需求隨意組合的拜效。

2.4、 NSCondition
NSCondition 是一種特殊類型的鎖各谚,通過它可以實現(xiàn)不同線程的調(diào)度紧憾。一個線程被某一個條件所阻塞,直到另一個線程滿足該條件從而發(fā)送信號給該線程使得該線程可以正確的執(zhí)行昌渤。比如說赴穗,你可以開啟一個線程下載圖片,一個線程處理圖片膀息。這樣的話般眉,需要處理圖片的線程由于沒有圖片會阻塞,當(dāng)下載線程下載完成之后履婉,則滿足了需要處理圖片的線程的需求煤篙,這樣可以給定一個信號,讓處理圖片的線程恢復(fù)運行毁腿。

  • NSCondition 的對象實際上作為一個鎖和一個線程檢查器辑奈,鎖上之后其它線程也能上鎖,而之后可以根據(jù)條件決定是否繼續(xù)運行線程已烤,即線程是否要進入 waiting 狀態(tài)鸠窗,如果進入 waiting 狀態(tài),當(dāng)其它線程中的該鎖執(zhí)行 signal 或者 broadcast 方法時胯究,線程被喚醒稍计,繼續(xù)運行之后的方法。
  • NSCondition 可以手動控制線程的掛起與喚醒裕循,可以利用這個特性設(shè)置依賴臣嚣。
    源碼解析:
@interface NSCondition : NSObject <NSLocking> {
@private
    void *_priv;
}
- (void)wait; //掛起線程
- (BOOL)waitUntilDate:(NSDate *)limit; //什么時候掛起線程
- (void)signal; // 喚醒一條掛起線程
- (void)broadcast; //喚醒所有掛起線程
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end

方法使用:

- (void)nscondition {
    NSCondition * cjcondition = [NSCondition new];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [cjcondition lock];
        NSLog(@"線程1線程加鎖");
        [cjcondition wait];
        NSLog(@"線程1線程喚醒");
        [cjcondition unlock];
        NSLog(@"線程1線程解鎖");
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [cjcondition lock];
        NSLog(@"線程2線程加鎖");
        if ([cjcondition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
            NSLog(@"線程2線程喚醒");
            [cjcondition unlock];
            NSLog(@"線程2線程解鎖");
        }
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        [cjcondition signal];//喚起第一條開啟的線程
        [cjcondition broadcast];//喚起所有線程
    });
}

2017-10-19 17:15:48.410316+0800 Thread-Lock[40011:943638] 線程1線程加鎖
2017-10-19 17:15:48.410757+0800 Thread-Lock[40011:943640] 線程2線程加鎖
2017-10-19 17:15:50.414288+0800 Thread-Lock[40011:943638] 線程1線程喚醒
2017-10-19 17:15:50.414454+0800 Thread-Lock[40011:943638] 線程1線程解鎖
由以上內(nèi)容總結(jié):
1、在加上鎖之后剥哑,調(diào)用條件對象的 wait 或 waitUntilDate: 方法來阻塞線程硅则,直到條件對象發(fā)出喚醒信號或者超時之后,再進行之后的操作株婴。
2怎虫、signal 和 broadcast 方法的區(qū)別在于,signal 只是一個信號量困介,只能喚醒一個等待的線程大审,想喚醒多個就得多次調(diào)用,而 broadcast 可以喚醒所有在等待的線程座哩。

3徒扶、 dispatch_semaphore 信號量實現(xiàn)加鎖(GCD)
dispatch_semaphore 使用信號量機制實現(xiàn)鎖,等待信號和發(fā)送信號根穷。

  • dispatch_semaphore 是 GCD 用來同步的一種方式酷愧,與他相關(guān)的只有三個函數(shù)驾诈,一個是創(chuàng)建信號量,一個是等待信號溶浴,一個是發(fā)送信號乍迄。
  • dispatch_semaphore 的機制就是當(dāng)有多個線程進行訪問的時候,只要有一個獲得了信號士败,其他線程的就必須等待該信號釋放闯两。
    相關(guān)的API:
  dispatch_semaphore_create(long value);
  dispatch_semaphore_wait(dispatch_semaphore_t _Nonnull dsema, dispatch_time_t timeout);
  dispatch_semaphore_signal(dispatch_semaphore_t _Nonnull dsema);

用法如下:

- (void)dispatch_semaphore {
     //創(chuàng)建信號量,必須大于1谅将,如果是N漾狼,表示同時可有N條線程執(zhí)行
     dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
     //給信號量設(shè)置最大等待時間,超過這個時間則不用等待
     dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 6 * NSEC_PER_SEC);
     
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
         dispatch_semaphore_wait(semaphore, overTime);
         NSLog(@"線程1開始");
         sleep(5);
         NSLog(@"線程1結(jié)束");
         dispatch_semaphore_signal(semaphore);
     });
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
         sleep(1);
         dispatch_semaphore_wait(semaphore, overTime);
         NSLog(@"線程2開始");
         dispatch_semaphore_signal(semaphore);
     });
 }

2017-10-19 18:30:37.672490+0800 Thread-Lock[40569:993613] 線程1開始
2017-10-19 18:30:42.673845+0800 Thread-Lock[40569:993613] 線程1結(jié)束
2017-10-19 18:30:42.674165+0800 Thread-Lock[40569:993612] 線程2開始
以上總結(jié):

  • dispatch_semaphore 和 NSCondition 類似饥臂,都是一種基于信號的同步方式逊躁,但 NSCondition 信號只能發(fā)送,不能保存(如果沒有線程在等待隅熙,則發(fā)送的信號會失效)稽煤。而 dispatch_semaphore 能保存發(fā)送的信號。dispatch_semaphore 的核心是 dispatch_semaphore_t 類型的信號量囚戚。
  • dispatch_semaphore_create(1) 方法可以創(chuàng)建一個 dispatch_semaphore_t 類型的信號量酵熙,設(shè)定信號量的初始值為 1。注意驰坊,這里的傳入的參數(shù)必須大于或等于 0匾二,否則 dispatch_semaphore_create 會返回 NULL。
  • dispatch_semaphore_wait(semaphore, overTime); 方法會判斷 semaphore 的信號值是否大于 0拳芙。大于 0 不會阻塞線程察藐,消耗掉一個信號,執(zhí)行后續(xù)任務(wù)舟扎。如果信號值為 0转培,該線程會和 NSCondition 一樣直接進入 waiting 狀態(tài),等待其他線程發(fā)送信號喚醒線程去執(zhí)行后續(xù)任務(wù)浆竭,或者當(dāng) overTime 時限到了,也會執(zhí)行后續(xù)任務(wù)惨寿。
  • dispatch_semaphore_signal(semaphore); 發(fā)送信號邦泄,如果沒有等待的線程接受信號,則使 signal 信號值加一(做到對信號的保存)裂垦。
  • 一個 dispatch_semaphore_wait(semaphore, overTime); 方法會去對應(yīng)一個 dispatch_semaphore_signal(semaphore); 看起來像 NSLock 的 lock 和 unlock顺囊,其實可以這樣理解,區(qū)別只在于有信號量這個參數(shù)蕉拢,lock unlock 只能同一時間特碳,一個線程訪問被保護的臨界區(qū)诚亚,而如果 dispatch_semaphore 的信號量初始值為 x ,則可以有 x 個線程同時訪問被保護的臨界區(qū)午乓。

4站宗、pthread_mutex與 pthread_mutex(recursive) 互斥鎖(C語言)
pthread 表示 POSIX thread,定義了一組跨平臺的線程相關(guān)的 API益愈,POSIX 互斥鎖是一種超級易用的互斥鎖梢灭,使用的時候:

  • 只需要使用 pthread_mutex_init 初始化一個 pthread_mutex_t,
  • pthread_mutex_lock 或者 pthread_mutex_trylock 來鎖定 蒸其,
  • pthread_mutex_unlock 來解鎖敏释,
  • 當(dāng)使用完成后,記得調(diào)用 pthread_mutex_destroy 來銷毀鎖摸袁。
    常用的相關(guān)的API:
   //初始化
   pthread_mutex_init(pthread_mutex_t *restrict _Nonnull, const pthread_mutexattr_t *restrict _Nullable);
  //加鎖
    pthread_mutex_lock(pthread_mutex_t * _Nonnull);
  //嘗試是鎖是否可用
    pthread_mutex_trylock(pthread_mutex_t * _Nonnull);
  //解鎖
    pthread_mutex_unlock(pthread_mutex_t * _Nonnull);

用法:

  - (void)pthread_mutex {
        __block pthread_mutex_t cjlock;
        pthread_mutex_init(&cjlock, NULL);
        
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            pthread_mutex_lock(&cjlock);
            NSLog(@"線程1開始");
            sleep(3);
            NSLog(@"線程1結(jié)束");
            pthread_mutex_unlock(&cjlock);
            
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(1);
            pthread_mutex_lock(&cjlock);
            NSLog(@"線程2");
            pthread_mutex_unlock(&cjlock);
        });
    }

5钥顽、OSSpinLock (暫不建議使用)
OSSpinLock 是一種自旋鎖,和互斥鎖類似靠汁,都是為了保證線程安全的鎖蜂大。但二者的區(qū)別是不一樣的,對于互斥鎖膀曾,當(dāng)一個線程獲得這個鎖之后县爬,其他想要獲得此鎖的線程將會被阻塞,直到該鎖被釋放添谊。但自選鎖不一樣财喳,當(dāng)一個線程獲得鎖之后,其他線程將會一直循環(huán)在哪里查看是否該鎖被釋放斩狱。所以耳高,此鎖比較適用于鎖的持有者保存時間較短的情況下。
只有加鎖所踊,解鎖泌枪,嘗試加鎖三個方法。
相關(guān)API:

 typedef int32_t OSSpinLock;
 // 加鎖
 void  OSSpinLockLock( volatile OSSpinLock *__lock );
 // 嘗試加鎖
 bool  OSSpinLockTry( volatile OSSpinLock *__lock );
 // 解鎖
 void  OSSpinLockUnlock( volatile OSSpinLock *__lock );

用法:

- (void)osspinlock {
    __block OSSpinLock theLock = OS_SPINLOCK_INIT;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        OSSpinLockLock(&theLock);
        NSLog(@"線程1開始");
        sleep(3);
        NSLog(@"線程1結(jié)束");
        OSSpinLockUnlock(&theLock);
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        OSSpinLockLock(&theLock);
        sleep(1);
        NSLog(@"線程2");
        OSSpinLockUnlock(&theLock);
    });

OSSpinLock 在iOS 10.0中被 <os/lock.h> 中的 os_unfair_lock 取代,同時OSSpinLock存在不安全性

6秕岛、os_unfair_lock
自旋鎖已經(jīng)不再安全碌燕,然后蘋果又整出來個 os_unfair_lock,這個鎖解決了優(yōu)先級反轉(zhuǎn)問題继薛。
常用相關(guān)API:

   // 初始化
    os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT);
    // 加鎖
    os_unfair_lock_lock(unfairLock);
    // 嘗試加鎖
    BOOL b = os_unfair_lock_trylock(unfairLock);
    // 解鎖
    os_unfair_lock_unlock(unfairLock);
    os_unfair_lock 用法和 OSSpinLock 基本一直修壕,就不一一列出了。

全文總結(jié)
應(yīng)當(dāng)針對不同的操作使用不同的鎖遏考,而不能一概而論哪種鎖的加鎖解鎖速度快慈鸠。
1. 其實每一種鎖基本上都是加鎖、等待灌具、解鎖的步驟青团,理解了這三個步驟就可以幫你快速的學(xué)會各種鎖的用法譬巫。
2. @synchronized 的效率最低,不過它的確用起來最方便督笆,所以如果沒什么性能瓶頸的話芦昔,可以選擇使用 @synchronized。
3. 當(dāng)性能要求較高時候胖腾,可以使用 pthread_mutex 或者 dispath_semaphore烟零,由于 OSSpinLock 不能很好的保證線程安全,而在只有在 iOS10 中才有 os_unfair_lock 咸作,所以锨阿,前兩個是比較好的選擇。既可以保證速度记罚,又可以保證線程安全墅诡。
文章開端已經(jīng)放了一張各個鎖的性能圖,大家可以根據(jù)項目需要選擇適合鎖桐智。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末末早,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子说庭,更是在濱河造成了極大的恐慌然磷,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刊驴,死亡現(xiàn)場離奇詭異姿搜,居然都是意外死亡,警方通過查閱死者的電腦和手機捆憎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門舅柜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人躲惰,你說我怎么就攤上這事致份。” “怎么了础拨?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵氮块,是天一觀的道長。 經(jīng)常有香客問我诡宗,道長滔蝉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任僚焦,我火速辦了婚禮,結(jié)果婚禮上曙痘,老公的妹妹穿的比我還像新娘芳悲。我一直安慰自己立肘,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布名扛。 她就那樣靜靜地躺著谅年,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肮韧。 梳的紋絲不亂的頭發(fā)上融蹂,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機與錄音弄企,去河邊找鬼超燃。 笑死,一個胖子當(dāng)著我的面吹牛拘领,可吹牛的內(nèi)容都是我干的意乓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼约素,長吁一口氣:“原來是場噩夢啊……” “哼届良!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起圣猎,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤士葫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后送悔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體慢显,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年放祟,在試婚紗的時候發(fā)現(xiàn)自己被綠了鳍怨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡跪妥,死狀恐怖鞋喇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情眉撵,我是刑警寧澤侦香,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站纽疟,受9級特大地震影響罐韩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜污朽,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一散吵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦矾睦、人聲如沸晦款。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缓溅。三九已至,卻和暖如春赁温,著一層夾襖步出監(jiān)牢的瞬間坛怪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工股囊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留袜匿,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓毁涉,卻偏偏與公主長得像沉帮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子贫堰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

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

  • 鎖是一種同步機制穆壕,用于多線程環(huán)境中對資源訪問的限制iOS中常見鎖的性能對比圖(摘自:ibireme): iOS鎖的...
    LiLS閱讀 1,516評論 0 6
  • 在平時的開發(fā)中經(jīng)常使用到多線程,在使用多線程的過程中其屏,難免會遇到資源競爭的問題喇勋,那我們怎么來避免出現(xiàn)這種問題那? ...
    IAMCJ閱讀 3,096評論 2 25
  • demo下載 建議一邊看文章偎行,一邊看代碼川背。 聲明:關(guān)于性能的分析是基于我的測試代碼來的,我也看到和網(wǎng)上很多測試結(jié)果...
    炸街程序猿閱讀 794評論 0 2
  • 線程安全是怎么產(chǎn)生的 常見比如線程內(nèi)操作了一個線程外的非線程安全變量蛤袒,這個時候一定要考慮線程安全和同步熄云。 - (v...
    幽城88閱讀 661評論 0 0
  • 本文節(jié)選自成長手冊 文章推薦和參考深入理解 iOS 開發(fā)中的鎖pthread的各種同步機制 多線程編程被普遍認為復(fù)...
    百草紀(jì)閱讀 2,802評論 1 9