iOS中常見(jiàn)的幾種鎖

線程安全是什么?

當(dāng)一個(gè)線程訪問(wèn)數(shù)據(jù)的時(shí)候,其他的線程不能對(duì)其進(jìn)行訪問(wèn),直到該線程訪問(wèn)完畢。簡(jiǎn)單來(lái)講就是在同一時(shí)刻,對(duì)同一個(gè)數(shù)據(jù)操作的線程只有一個(gè)。只有確保了這樣,才能使數(shù)據(jù)不會(huì)被其他線程影響。而線程不安全,則是在同一時(shí)刻可以有多個(gè)線程對(duì)該數(shù)據(jù)進(jìn)行訪問(wèn),從而得不到預(yù)期的結(jié)果。

比如寫文件和讀文件,當(dāng)一個(gè)線程在寫文件的時(shí)候,理論上來(lái)說(shuō),如果這個(gè)時(shí)候另一個(gè)線程來(lái)直接讀取的話,那么得到的結(jié)果可能是你無(wú)法預(yù)料的。

什么情況下會(huì)造成死鎖?

死鎖就是隊(duì)列引起的循環(huán)等待
1、一個(gè)比較常見(jiàn)的死鎖例子:主隊(duì)列同步

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"deallock");
    });
}

同步對(duì)于任務(wù)是立刻執(zhí)行的,那么當(dāng)把任務(wù)放進(jìn)主隊(duì)列時(shí),它就會(huì)立馬執(zhí)行,只有執(zhí)行完這個(gè)任務(wù),viewDidLoad才會(huì)繼續(xù)向下執(zhí)行。
而viewDidLoad和任務(wù)都是在主隊(duì)列上的,由于隊(duì)列的先進(jìn)先出原則,任務(wù)又需等待viewDidLoad執(zhí)行完畢后才能繼續(xù)執(zhí)行,viewDidLoad和這個(gè)任務(wù)就形成了相互循環(huán)等待,就造成了死鎖。
想避免這種死鎖,可以將同步改成異步dispatch_async,或者將dispatch_get_main_queue換成其他串行或并行隊(duì)列,都可以解決。

2、串行隊(duì)列(當(dāng)然也包括主隊(duì)列)中向這個(gè)隊(duì)列同步添加任務(wù)都會(huì)造成死鎖

異步執(zhí)行一定會(huì)開(kāi)啟子線程嗎???
不一定,在主線程中異步+主隊(duì)列就不會(huì)

    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"異步執(zhí)行一定會(huì)創(chuàng)建子線程嗎???");
        NSLog(@"%@",[NSThread currentThread]);
    });
//打印結(jié)果:
 <NSThread: 0x600003154d80>{number = 1, name = main}

怎么來(lái)保證線程安全?

通常我們使用鎖的機(jī)制來(lái)保證線程安全,即確保同一時(shí)刻只有同一個(gè)線程來(lái)對(duì)同一個(gè)數(shù)據(jù)源進(jìn)行訪問(wèn)。

YY大神 的 不再安全的 OSSpinLock 這邊博客中列出了各種鎖以及性能比較:

image.png

這里性能比較的只是加鎖立馬解鎖的時(shí)間消耗,并沒(méi)有計(jì)算競(jìng)爭(zhēng)時(shí)候的時(shí)間消耗。iOS開(kāi)發(fā)中常用的鎖有如下幾種:

  1. @synchronized
  2. NSLock 對(duì)象鎖
  3. NSRecursiveLock 遞歸鎖
  4. NSConditionLock 條件鎖
  5. pthread_mutex 互斥鎖(C語(yǔ)言)
  6. dispatch_semaphore 信號(hào)量實(shí)現(xiàn)加鎖(GCD)
  7. OSSpinLock 自旋鎖(暫不建議使用,原因參見(jiàn)這里

1.@synchronized

@synchronized 關(guān)鍵字加鎖 互斥鎖,性能較差不推薦使用

@synchronized(這里添加一個(gè)OC對(duì)象,一般使用self) { 
        //這里寫要加鎖的代碼
 }  

注意點(diǎn)   
1.加鎖的代碼盡量少    
2.添加的OC對(duì)象必須在多個(gè)線程中都是同一對(duì)象 
3.優(yōu)點(diǎn)是不需要顯式的創(chuàng)建鎖對(duì)象,便可以實(shí)現(xiàn)鎖的機(jī)制。 
4. @synchronized塊會(huì)隱式的添加一個(gè)異常處理例程來(lái)保護(hù)代碼,該處理例程會(huì)在異常拋出的時(shí)候自動(dòng)的釋放互斥鎖。
所以如果不想讓隱式的異常處理例程帶來(lái)額外的開(kāi)銷,你可以考慮使用鎖對(duì)象。

下面通過(guò) 賣票的例子 展示使用:
-(void)synchronizedTest{
    //設(shè)置票的數(shù)量為5
     _tickets = 5;
    //線程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self saleTickets];
    });
    
    //線程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self saleTickets];
    });
}

- (void)saleTickets {
    while (1) {
        @synchronized(self) {
            [NSThread sleepForTimeInterval:1];
            if (_tickets > 0) {
                _tickets--;
                NSLog(@"剩余票數(shù)= %d, Thread:%@",_tickets,[NSThread currentThread]);
            }else {
                NSLog(@"票賣完了 Thread:%@",[NSThread currentThread]);
                break;
            }
        }
    }
}

下面是加鎖的和沒(méi)加鎖的對(duì)比:


image.png

image.png

NSLock

先看看iOS中NSLock類的.h文件,從代碼中可以看出,該類分成了幾個(gè)子類:NSLockNSConditionLock、NSRecursiveLock、NSCondition,然后有一個(gè) NSLocking 協(xié)議

@protocol NSLocking

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

@end

雖然 NSLock、NSConditionLock、NSRecursiveLock、NSCondition 都遵循的了 NSLocking 協(xié)議,但是它們并不相同。

2.1 NSLock
NSLock 實(shí)現(xiàn)了最基本的互斥鎖,遵循了 NSLocking 協(xié)議,通過(guò) lock 和 unlock 來(lái)進(jìn)行鎖定和解鎖。

源碼內(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

NSLock類還增加了tryLocklockBeforeDate:方法。
tryLock試圖獲取一個(gè)鎖,但是如果鎖不可用的時(shí)候,它不會(huì)阻塞線程,相反,它只是返回NO。
這里順便提一下 trylock 和 lock 使用場(chǎng)景:
當(dāng)前線程鎖失敗,也可以繼續(xù)其它任務(wù),用 trylock 合適;當(dāng)前線程只有鎖成功后,才會(huì)做一些有意義的工作,那就 lock,沒(méi)必要輪詢 trylock。以下的鎖都是這樣。

lockBeforeDate:方法試圖獲取一個(gè)鎖,但是如果鎖沒(méi)有在規(guī)定的時(shí)間內(nèi)被獲得,它會(huì)讓線程從阻塞狀態(tài)變?yōu)榉亲枞麪顟B(tài)(或者返回NO)。

注意:NSLock 互斥鎖 不能多次調(diào)用 lock方法,會(huì)造成死鎖

-(void)NSLockTest{
    //設(shè)置票的數(shù)量為5
    _tickets = 5;
    //創(chuàng)建鎖
    _mutexLock = [[NSLock alloc] init];
    //線程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self saleTickets];
    });
    
    //線程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self saleTickets];
    });
}

- (void)saleTickets {
    while (1) {
        [NSThread sleepForTimeInterval:1];
        //加鎖
        [_mutexLock lock];
        if (_tickets > 0) {
            _tickets--;
            NSLog(@"剩余票數(shù)= %d, Thread:%@",_tickets,[NSThread currentThread]);
        } else {
            NSLog(@"票賣完了 Thread:%@",[NSThread currentThread]); break;
        }
        //解鎖
        [_mutexLock unlock];
    }
}
image.png

2.2 NSRecursiveLock
NSRecursiveLock 是遞歸鎖,顧名思義,可以被一個(gè)線程多次獲得,而不會(huì)引起死鎖。它記錄了成功獲得鎖的次數(shù),每一次成功的獲得鎖,必須有一個(gè)配套的釋放鎖和其對(duì)應(yīng),這樣才不會(huì)引起死鎖。NSRecursiveLock 會(huì)記錄上鎖和解鎖的次數(shù),當(dāng)二者平衡的時(shí)候,才會(huì)釋放鎖,其它線程才可以上鎖成功。

源碼內(nèi)容:

@interface NSRecursiveLock : 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)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);
    });
}
image.png

由以上內(nèi)容總結(jié):

如果用 NSLock 的話,cjlock 先鎖上了,但未執(zhí)行解鎖的時(shí)候,就會(huì)進(jìn)入遞歸的下一層,而再次請(qǐng)求上鎖,阻塞了該線程,線程被阻塞了,自然后面的解鎖代碼不會(huì)執(zhí)行,而形成了死鎖。NSRecursiveLock 遞歸鎖就是為了解決這個(gè)問(wèn)題。

2.3 NSConditionLock
NSConditionLock 對(duì)象所定義的互斥鎖可以在使得在某個(gè)條件下進(jìn)行鎖定和解鎖,它和 NSLock 類似,都遵循 NSLocking 協(xié)議,方法都類似,只是多了一個(gè) condition 屬性,以及每個(gè)操作都多了一個(gè)關(guān)于 condition 屬性的方法,例如tryLock、tryLockWhenCondition:,所以 NSConditionLock 可以稱為條件鎖。

  • 只有 condition 參數(shù)與初始化時(shí)候的 condition 相等,lock 才能正確進(jìn)行加鎖操作。
  • unlockWithCondition: 并不是當(dāng) condition 符合條件時(shí)才解鎖,而是解鎖之后,修改 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嘗試加鎖失敗");
        }
    });
}
image.png

由以上內(nèi)容總結(jié):

  • 在線程 1 解鎖成功之后,線程 2 并沒(méi)有加鎖成功,而是繼續(xù)等了 1 秒之后線程 3 加鎖成功,這是因?yàn)榫€程 2 的加鎖條件不滿足,初始化時(shí)候的 condition 參數(shù)為 0,而線程 2
  • 加鎖條件是 condition 為 1,所以線程 2 加鎖失敗。
  • lockWhenCondition 與 lock 方法類似,加鎖失敗會(huì)阻塞線程,所以線程 2 會(huì)被阻塞著。
  • tryLockWhenCondition: 方法就算條件不滿足,也會(huì)返回 NO,不會(huì)阻塞當(dāng)前線程。
  • lockWhenCondition:beforeDate:方法會(huì)在約定的時(shí)間內(nèi)一直等待 condition 變?yōu)?2,并阻塞當(dāng)前線程,直到超時(shí)后返回 NO。
  • 鎖定和解鎖的調(diào)用可以隨意組合,也就是說(shuō) lock、lockWhenCondition:與unlock、unlockWithCondition: 是可以按照自己的需求隨意組合的。

2.4、NSCondition
NSCondition 是一種特殊類型的鎖,通過(guò)它可以實(shí)現(xiàn)不同線程的調(diào)度。一個(gè)線程被某一個(gè)條件所阻塞,直到另一個(gè)線程滿足該條件從而發(fā)送信號(hào)給該線程使得該線程可以正確的執(zhí)行。比如說(shuō),你可以開(kāi)啟一個(gè)線程下載圖片,一個(gè)線程處理圖片。這樣的話,需要處理圖片的線程由于沒(méi)有圖片會(huì)阻塞,當(dāng)下載線程下載完成之后,則滿足了需要處理圖片的線程的需求,這樣可以給定一個(gè)信號(hào),讓處理圖片的線程恢復(fù)運(yùn)行。

  • NSCondition 的對(duì)象實(shí)際上作為一個(gè)鎖和一個(gè)線程檢查器,鎖上之后其它線程也能上鎖,而之后可以根據(jù)條件決定是否繼續(xù)運(yùn)行線程,即線程是否要進(jìn)入waiting 狀態(tài),如果進(jìn)入 waiting狀態(tài),當(dāng)其它線程中的該鎖執(zhí)行 signal或者broadcast方法時(shí),線程被喚醒,繼續(xù)運(yùn)行之后的方法。
  • NSCondition 可以手動(dòng)控制線程的掛起與喚醒,可以利用這個(gè)特性設(shè)置依賴。

源碼內(nèi)容:

@interface NSCondition : NSObject <NSLocking> {
@private
  void *_priv;
}

- (void)wait; //掛起線程
- (BOOL)waitUntilDate:(NSDate *)limit; //什么時(shí)候掛起線程
- (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];
  });
}
image.png
//如果 [cjcondition signal]; 改成 [cjcondition broadcast];
  dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(2);
    [cjcondition broadcast];
  });
image.png

由以上內(nèi)容總結(jié):

  • 在加上鎖之后,調(diào)用條件對(duì)象的 waitwaitUntilDate:方法來(lái)阻塞線程,直到條件對(duì)象發(fā)出喚醒信號(hào)或者超時(shí)之后,再進(jìn)行之后的操作。
  • signal 和 broadcast 方法的區(qū)別在于,signal只是一個(gè)信號(hào)量,只能喚醒一個(gè)等待的線程,想喚醒多個(gè)就得多次調(diào)用,而broadcast 可以喚醒所有在等待的線程。

3.dispatch_semaphore

dispatch_semaphore 使用信號(hào)量機(jī)制實(shí)現(xiàn)鎖,等待信號(hào)和發(fā)送信號(hào)。

  • dispatch_semaphore 是 GCD 用來(lái)同步的一種方式,與他相關(guān)的只有三個(gè)函數(shù),一個(gè)是創(chuàng)建信號(hào)量,一個(gè)是等待信號(hào),一個(gè)是發(fā)送信號(hào)。
  • dispatch_semaphore 的機(jī)制就是當(dāng)有多個(gè)線程進(jìn)行訪問(wèn)的時(shí)候,只要有一個(gè)獲得了信號(hào),其他線程的就必須等待該信號(hào)釋放。

常用相關(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);

實(shí)例代碼:

- (void)dispatch_semaphore {
  dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
  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開(kāi)始");
    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開(kāi)始");
    dispatch_semaphore_signal(semaphore);
  });
}
image.png
//如果 overTime 改成 3 秒
image.png

由以上內(nèi)容總結(jié):

  • dispatch_semaphoreNSCondition 類似,都是一種基于信號(hào)的同步方式,但 NSCondition 信號(hào)只能發(fā)送,不能保存(如果沒(méi)有線程在等待,則發(fā)送的信號(hào)會(huì)失效)。而 dispatch_semaphore 能保存發(fā)送的信號(hào)。dispatch_semaphore 的核心是 dispatch_semaphore_t 類型的信號(hào)量。
  • dispatch_semaphore_create(1)方法可以創(chuàng)建一個(gè) dispatch_semaphore_t 類型的信號(hào)量,設(shè)定信號(hào)量的初始值為 1。注意,這里的傳入的參數(shù)必須大于或等于 0,否則 dispatch_semaphore_create 會(huì)返回 NULL。
    *dispatch_semaphore_wait(semaphore, overTime); 方法會(huì)判斷 semaphore 的信號(hào)值是否大于 0。大于 0 不會(huì)阻塞線程,消耗掉一個(gè)信號(hào),執(zhí)行后續(xù)任務(wù)。如果信號(hào)值為 0,該線程會(huì)和 NSCondition 一樣直接進(jìn)入 waiting 狀態(tài),等待其他線程發(fā)送信號(hào)喚醒線程去執(zhí)行后續(xù)任務(wù),或者當(dāng) overTime 時(shí)限到了,也會(huì)執(zhí)行后續(xù)任務(wù)。
  • dispatch_semaphore_signal(semaphore);發(fā)送信號(hào),如果沒(méi)有等待的線程接受信號(hào),則使 signal 信號(hào)值加一(做到對(duì)信號(hào)的保存)。
  • 一個(gè) dispatch_semaphore_wait(semaphore, overTime); 方法會(huì)去對(duì)應(yīng)一個(gè) dispatch_semaphore_signal(semaphore); 看起來(lái)像 NSLock 的 lock 和 unlock,其實(shí)可以這樣理解,區(qū)別只在于有信號(hào)量這個(gè)參數(shù),lock unlock 只能同一時(shí)間,一個(gè)線程訪問(wèn)被保護(hù)的臨界區(qū),而如果 dispatch_semaphore 的信號(hào)量初始值為 x ,則可以有 x 個(gè)線程同時(shí)訪問(wèn)被保護(hù)的臨界區(qū)。

4.pthread_mutex 與 pthread_mutex(recursive)

pthread表示 POSIX thread,定義了一組跨平臺(tái)的線程相關(guān)的 API,POSIX 互斥鎖是一種超級(jí)易用的互斥鎖,使用的時(shí)候:

  • 只需要使用pthread_mutex_init 初始化一個(gè) pthread_mutex_t,
  • pthread_mutex_lock 或者 pthread_mutex_trylock 來(lái)鎖定 ,
  • pthread_mutex_unlock來(lái)解鎖,
  • 當(dāng)使用完成后,記得調(diào)用 pthread_mutex_destroy來(lái)銷毀鎖。

常用相關(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);
pthread_mutex_destroy(pthread_mutex_t * _Nonnull);

示例代碼:

//記得導(dǎo)入頭文件
 #include <pthread.h>

- (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開(kāi)始");
    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);
  });
}
image.png
//pthread_mutex(recursive)

- (void)pthread_mutex_recursive {
  __block pthread_mutex_t cjlock;
  
  pthread_mutexattr_t attr;
  pthread_mutexattr_init(&attr);
  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
  pthread_mutex_init(&cjlock, &attr);
  pthread_mutexattr_destroy(&attr);
  
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
    static void (^RecursiveBlock)(int);
    
    RecursiveBlock = ^(int value) {
      pthread_mutex_lock(&cjlock);
      NSLog(@"%d加鎖成功",value);
      if (value > 0) {
        NSLog(@"value = %d", value);
        sleep(1);
        RecursiveBlock(value - 1);
      }
      NSLog(@"%d解鎖成功",value);
      pthread_mutex_unlock(&cjlock);
    };
    RecursiveBlock(3);
  });
}
image.png

由以上內(nèi)容總結(jié):

  • 它的用法和 NSLock 的 lock unlock 用法一致,而它也有一個(gè) pthread_mutex_trylock 方法,pthread_mutex_trylock 和 tryLock 的區(qū)別在于,tryLock 返回的是 YES 和 NO,pthread_mutex_trylock 加鎖成功返回的是 0,失敗返回的是錯(cuò)誤提示碼。
  • pthread_mutex(recursive) 作用和 NSRecursiveLock 遞歸鎖類似。如果使用 pthread_mutex_init(&theLock, NULL); 初始化鎖的話,上面的代碼的第二部分會(huì)出現(xiàn)死鎖現(xiàn)象,使用遞歸鎖就可以避免這種現(xiàn)象。

5. OSSpinLock

OSSpinLock 是一種自旋鎖,和互斥鎖類似,都是為了保證線程安全的鎖。但二者的區(qū)別是不一樣的,對(duì)于互斥鎖,當(dāng)一個(gè)線程獲得這個(gè)鎖之后,其他想要獲得此鎖的線程將會(huì)被阻塞,直到該鎖被釋放。但自選鎖不一樣,當(dāng)一個(gè)線程獲得鎖之后,其他線程將會(huì)一直循環(huán)在哪里查看是否該鎖被釋放。所以,此鎖比較適用于鎖的持有者保存時(shí)間較短的情況下。

自旋鎖加鎖的時(shí)候,等待鎖的線程處于忙等狀態(tài),并且占用著CPU的資源。
互斥鎖加鎖的時(shí)候,等待鎖的線程處于休眠狀態(tài),不會(huì)占用CPU的資源。

只有加鎖,解鎖,嘗試加鎖三個(gè)方法。

常用相關(guān)API:

typedef int32_t OSSpinLock;

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

示例代碼:

//使用的時(shí)候Xcode會(huì)提示已過(guò)期,使用os_unfair_lock()替代
'OSSpinLock' is deprecated: first deprecated in iOS 10.0 - Use os_unfair_lock() from <os/lock.h> instead

#import <libkern/OSAtomic.h>

- (void)osspinlock {
    __block OSSpinLock theLock = OS_SPINLOCK_INIT;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        OSSpinLockLock(&theLock);
        NSLog(@"線程1開(kāi)始");
        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);
        
    });
}
image.png

YY大神 @ibireme 的文章也有說(shuō)這個(gè)自旋鎖存在優(yōu)先級(jí)反轉(zhuǎn)問(wèn)題,具體文章可以戳 不再安全的 OSSpinLock,而 OSSpinLock 在iOS 10.0中被 <os/lock.h> 中的 os_unfair_lock 取代。

6.os_unfair_lock

自旋鎖已經(jīng)不再安全,然后蘋果又整出來(lái)個(gè) os_unfair_lock,這個(gè)鎖解決了優(yōu)先級(jí)反轉(zhuǎn)問(wèn)題。

注意:os_unfair_lock 是互斥鎖

常用相關(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)針對(duì)不同的操作使用不同的鎖,而不能一概而論哪種鎖的加鎖解鎖速度快。

其實(shí)每一種鎖基本上都是加鎖、等待、解鎖的步驟,理解了這三個(gè)步驟就可以幫你快速的學(xué)會(huì)各種鎖的用法。

@synchronized 的效率最低,不過(guò)它的確用起來(lái)最方便,所以如果沒(méi)什么性能瓶頸的話,可以選擇使用 @synchronized。

當(dāng)性能要求較高時(shí)候,可以使用pthread_mutex 或者 dispath_semaphore,由于 OSSpinLock 不能很好的保證線程安全,而在只有在 iOS10 中才有 os_unfair_lock ,所以,前兩個(gè)是比較好的選擇。既可以保證速度,又可以保證線程安全。

對(duì)于 NSLock 及其子類,時(shí)間消耗來(lái)說(shuō) NSLock < NSCondition < NSRecursiveLock < NSConditionLock 。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 鎖是一種同步機(jī)制,用于多線程環(huán)境中對(duì)資源訪問(wèn)的限制iOS中常見(jiàn)鎖的性能對(duì)比圖(摘自:ibireme): iOS鎖的...
    LiLS閱讀 1,625評(píng)論 0 6
  • 線程安全是怎么產(chǎn)生的 常見(jiàn)比如線程內(nèi)操作了一個(gè)線程外的非線程安全變量,這個(gè)時(shí)候一定要考慮線程安全和同步。 - (v...
    幽城88閱讀 758評(píng)論 0 0
  • 自旋鎖和互斥鎖 共同點(diǎn):都能保證同一時(shí)刻只能有一個(gè)線程操作鎖住的代碼。都能保證線程安全。不同點(diǎn): 互斥鎖(mute...
    中軸線_lz閱讀 799評(píng)論 0 0
  • 鎖是最常用的同步工具。一段代碼段在同一個(gè)時(shí)間只能允許被有限個(gè)線程訪問(wèn),比如一個(gè)線程 A 進(jìn)入需要保護(hù)代碼之前添加簡(jiǎn)...
    沒(méi)八阿哥的程序閱讀 832評(píng)論 0 0
  • (轉(zhuǎn)載) iOS 各種鎖機(jī)制 一、前言 前段時(shí)間看了幾個(gè)開(kāi)源項(xiàng)目,發(fā)現(xiàn)他們保持線程同步的方式各不相同,有@syn...
    北漂老張閱讀 487評(píng)論 0 1

友情鏈接更多精彩內(nèi)容