OC底層原理 - 23 iOS中的鎖

引言

鎖是開發(fā)中最常用的同步工具,通過鎖來實(shí)現(xiàn)對臨界資源的訪問控制,從而使目標(biāo)代碼段同一時(shí)間只會(huì)被一個(gè)線程執(zhí)行。這是一種以犧牲性能為代價(jià)的方法。

鎖的實(shí)現(xiàn)依賴于原子操作,不同的處理器(intel、arm),不同的架構(gòu)(單核、多核)實(shí)現(xiàn)原子操作的方式不一樣。有的是通過加鎖封鎖總線,有的是做成單指令,有的是依據(jù)標(biāo)志位,有的是依據(jù)CPU相關(guān)的指令對,總之,不同的機(jī)制可以實(shí)現(xiàn)原子操作。

原子操作,就像原子一樣不可再分割的操作,即:一個(gè)操作(有可能包含多個(gè)子操作)只要開始執(zhí)行,在執(zhí)行完畢前,不會(huì)被其它操作或者指令中斷。原子操作解決了多線程不安全問題中的原子性問題。如果沒有原子操作的話,操作可能會(huì)因?yàn)橹袛喈惓5雀鞣N原因引起數(shù)據(jù)狀態(tài)的不一致,從而影響到程序的正確性。

iOS中的atomic屬性修飾符的語義就是原子操作。被atomic所修飾的屬性,確保了setter和getter的原子性,這使得setter和getter這兩個(gè)方法是線程安全的,但是對于整個(gè)對象來說,不一定是線程安全的。并且atomic比nonatomic開銷要大很多,所以一般考慮到性能時(shí),會(huì)將屬性修飾符設(shè)置為nonatomic。

雖然鎖是同步兩個(gè)線程的有效辦法,但是獲取鎖是一個(gè)相對昂貴的操作,即使在無爭用的情況下,也是如此。相比之下,許多原子操作只需要花費(fèi)一小部分時(shí)間就可以完成,并且可以像鎖一樣有效。

使用鎖可以保證多線程操作共享數(shù)據(jù)時(shí)的安全問題,卻也降低了程序的執(zhí)行效率。鎖的這種機(jī)制無法徹底避免以下幾點(diǎn)問題:
① 鎖引起的線程阻塞,對于沒有能占用到鎖的線程或者進(jìn)程將會(huì)一直等待鎖的占有者釋放資源后才能繼續(xù)。
② 申請和釋放鎖的操作增加了很多訪問共享資源的消耗。
③ 鎖不能很好的避免編程開發(fā)者設(shè)計(jì)實(shí)現(xiàn)的程序出現(xiàn)死鎖或者活鎖的可能。
④ 優(yōu)先級(jí)反轉(zhuǎn)和鎖護(hù)送怪現(xiàn)象。
⑤ 難以調(diào)試。

鎖的分類

鎖的分類多種多樣,根據(jù)線程的狀態(tài)可以分為:互斥鎖自旋鎖

互斥鎖:互斥鎖充當(dāng)資源周圍的保護(hù)屏障,如果多個(gè)線程競爭同一個(gè)互斥鎖,每次只允許一個(gè)線程訪問。如果一個(gè)互斥鎖正在使用中,另一個(gè)線程試圖獲取它,該線程就會(huì)阻塞,進(jìn)入睡眠狀態(tài),直到該互斥鎖被它的持有者釋放后再將其喚醒。注意:互斥鎖阻塞的線程處于休眠狀態(tài)。

自旋鎖:如果一個(gè)自旋鎖正在使用中,另一個(gè)線程試圖獲取它時(shí),該線程不會(huì)進(jìn)入睡眠狀態(tài),而是反復(fù)輪詢其鎖條件,直到該條件為真。這適用于競爭預(yù)期較低的情況。注意:自旋鎖阻塞的線程處于忙等狀態(tài)

使線程進(jìn)入睡眠狀態(tài),主動(dòng)讓出時(shí)間片并不代表效率高,因?yàn)椴僮飨到y(tǒng)切換到另一個(gè)線程上下文時(shí),通常需要10ms,而且需要切換兩次。因此,如果鎖的預(yù)期等待時(shí)間很短,輪詢通常比線程休眠更有效。

iOS中的鎖

轉(zhuǎn)載自iOS多線程編程(七) 同步機(jī)制與鎖

pthread_mutex 互斥鎖

互斥鎖是一種用來防止多個(gè)線程同一時(shí)刻對共享資源進(jìn)行訪問的信號(hào)量,它的原子性確保了如果一個(gè)線程鎖定了一個(gè)互斥量,將沒有其他線程在同一時(shí)間可以鎖定這個(gè)互斥量。它的唯一性確保了只有它解鎖了這個(gè)互斥量,其他線程才可以對其進(jìn)行鎖定。當(dāng)一個(gè)線程鎖定一個(gè)資源的時(shí)候,其他對該資源進(jìn)行訪問的線程將會(huì)被掛起,直到該線程解鎖了互斥量,其他線程才會(huì)被喚醒,進(jìn)一步才能鎖定該資源進(jìn)行操作。

pthread_mutex是POSIX提供的互斥鎖,基于C語言實(shí)現(xiàn),可跨平臺(tái)。基本上OC層面的互斥鎖都是基于pthread_mutex實(shí)現(xiàn)的。主要的函數(shù)如下:

// 宏定義。用于靜態(tài)的mutex的初始化,采用默認(rèn)的attr。
PTHREAD_MUTEX_INITIALIZER 
// 用于動(dòng)態(tài)的mutex的初始化,第二個(gè)參數(shù)為mutex的屬性attr
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 
// 請求獲得鎖,如果當(dāng)前mutex未被持有,則加鎖成功;
// 如果當(dāng)前mutex已被持有,那么請求加鎖線程不會(huì)獲得成功,并阻塞線程,直到mutex被釋放
int pthread_mutex_lock(pthread_mutex_t *mutex); 
// 釋放鎖
int pthread_mutex_unlock(pthread_mutex_t *mutex); 
// 嘗試獲得鎖,如果當(dāng)前mutex已經(jīng)被持有或者不可用,這個(gè)函數(shù)就直接return,不會(huì)阻塞當(dāng)前線程
int pthread_mutex_trylock(pthread_mutex_t *mutex); 
// 銷毀mutex鎖,并且釋放所有它所占有的資源
int pthread_mutex_destroy(pthread_mutex_t *mutex); 

使用pthread_mutex的主要過程為:

  • ① 創(chuàng)建pthread_mutex;
  • ② 使用pthread_mutex_lock加鎖,使用pthread_mutex_unlock解鎖;
  • ③ 銷毀pthread_mutex;

創(chuàng)建pthread_mutex:

初始化pthread_mutex有兩種方式,一種是通過宏定義(PTHREAD_MUTEX_INITIALIZER)獲得默認(rèn)的互斥鎖,另一種是通過函數(shù)(pthread_mutex_init )創(chuàng)建鎖。如果不需要自定義pthread_mutex的屬性信息,使用宏定義的方式更快速便捷。

使用pthread_mutex_lock加鎖與pthread_mutex_unlock解鎖

pthread_mutex(互斥鎖)利用排他性來保證線程安全,在同一時(shí)刻只允許一個(gè)線程獲得鎖。如果一個(gè)線程已經(jīng)獲得互斥鎖,另一個(gè)線程就無法訪問,直到鎖的持有者正確的釋放了互斥鎖,另一個(gè)線程才有機(jī)會(huì)獲得鎖。

- (void)pthread_mutexDemo {
  
    // 創(chuàng)建mutex
    __block pthread_mutex_t mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
    // 線程1
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        pthread_mutex_lock(&mutex);
        NSLog(@"執(zhí)行任務(wù)A---%@",[NSThread currentThread]);
        sleep(5);
        NSLog(@"任務(wù)A執(zhí)行完畢---%@",[NSThread currentThread]);
        pthread_mutex_unlock(&mutex);
    });
    // 線程2
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(1);// 讓線程1的任務(wù)先執(zhí)行
        pthread_mutex_lock(&mutex);
        NSLog(@"執(zhí)行任務(wù)B---%@",[NSThread currentThread]);
        pthread_mutex_unlock(&mutex);
    });
    
    // 銷毀mutex:確保mutex使用完畢再銷毀
//    pthread_mutex_destroy(&mutex);
}

// 打印結(jié)果:
2021-02-27 21:00:14.034887+0800 lockDemo[83241:6444414] 執(zhí)行任務(wù)A---<NSThread: 0x600000fad000>{number = 5, name = (null)}
2021-02-27 21:00:19.039718+0800 lockDemo[83241:6444414] 任務(wù)A執(zhí)行完畢---<NSThread: 0x600000fad000>{number = 5, name = (null)}
2021-02-27 21:00:19.040232+0800 lockDemo[83241:6444416] 執(zhí)行任務(wù)B---<NSThread: 0x600000faee80>{number = 3, name = (null)}

本例中,線程1先獲得互斥鎖,盡管線程2的異步任務(wù)在sleep(1)后就可執(zhí)行,但此時(shí)線程1已持有互斥鎖,所以再次遇到pthread_mutex_lock(&mutex)時(shí),必須等待,此時(shí)線程2處于阻塞態(tài),直到 sleep(5)后線程1釋放互斥鎖,線程2才被喚醒繼續(xù)執(zhí)行任務(wù)。

使用pthread_mutex_trylock

除了pthread_mutex_lock函數(shù)外,pthread_mutex還提供了pthread_mutex_trylock 函數(shù),與pthread_mutex_lock不同的是,使用pthread_mutex_trylock 函數(shù)來申請加鎖,不管是否能獲得鎖都立即返回,并不阻塞線程。如果申請失敗則返回錯(cuò)誤:EBUSY(鎖尚未解除)或者EINVAL(鎖變量不可用)。一旦在trylock的時(shí)候有錯(cuò)誤返回,那就把前面已經(jīng)拿到的鎖全部釋放,然后過一段時(shí)間再來一遍。

如果將上例中線程2的pthread_mutex_lock(&mutex)操作,換成pthread_mutex_trylock(&mutex)。則結(jié)果為

2021-02-27 21:04:54.976015+0800 lockDemo[62208:9380951] 執(zhí)行任務(wù)A---<NSThread: 0x6000017de040>{number = 6, name = (null)}
2021-02-27 21:04:55.977173+0800 lockDemo[62208:9380952] 執(zhí)行任務(wù)B---<NSThread: 0x6000017a5980>{number = 4, name = (null)}
2021-02-27 21:04:59.980902+0800 lockDemo[62208:9380951] 任務(wù)A執(zhí)行完畢---<NSThread: 0x6000017de040>{number = 6, name = (null)}

注意事項(xiàng)

  • 避免多次申請鎖或釋放未獲得的鎖

使用pthread_mutex時(shí),pthread_mutex_lock與pthread_mutex_unlock要成對使用,一般情況下,一個(gè)線程只能申請一次鎖,也只能在獲得鎖的情況下才能釋放鎖,多次申請鎖或釋放未獲得的鎖都會(huì)導(dǎo)致異常。一定要確保在正確的時(shí)機(jī)獲得鎖和釋放鎖。

  • 避免阻塞

假設(shè)在已經(jīng)獲得鎖的情況下再次申請鎖,線程會(huì)因?yàn)榈却i的釋放而進(jìn)入睡眠狀態(tài),同時(shí)也不可能釋放鎖。

  • 避免死鎖

如果兩個(gè)線程存在互相等待釋放鎖的情況,也會(huì)導(dǎo)致死鎖的發(fā)生。

  • 記得pthread_mutex_destroy銷毀鎖,但要確保pthread_mutex已使用完畢。

pthread_mutex(recursive) 遞歸鎖

在實(shí)際開發(fā)中,有可能存在這樣的需求,遞歸調(diào)用或需要重復(fù)的獲得鎖。這種情況下,如果使用pthread_mutex(互斥鎖)就會(huì)阻塞線程,任務(wù)也就無法繼續(xù)執(zhí)行。這就需要使用遞歸鎖來解決問題了。

遞歸鎖是互斥鎖的變體。遞歸鎖允許單個(gè)線程在釋放鎖之前多次獲取該鎖(可重入,保存了鎖的次數(shù)信息)。而不會(huì)阻塞當(dāng)前線程,其他線程仍然處于阻塞狀態(tài),直到鎖的持有者以獲得鎖的相同次數(shù)釋放鎖。

遞歸鎖主要在遞歸迭代期間使用,也可以在多個(gè)方法分別需要獲得鎖的情況下使用。

遞歸鎖的使用:

pthread_mutex維護(hù)了以下幾種鎖類型:

/*
 * Mutex type attributes
 */
#define PTHREAD_MUTEX_NORMAL        0     // 普通互斥鎖
#define PTHREAD_MUTEX_ERRORCHECK    1     // 檢查鎖
#define PTHREAD_MUTEX_RECURSIVE     2     // 遞歸鎖
#define PTHREAD_MUTEX_DEFAULT       PTHREAD_MUTEX_NORMAL

PTHREAD_MUTEX_NORMAL是默認(rèn)屬性的互斥鎖;與PTHREAD_MUTEX_DEFAULT等同。
PTHREAD_MUTEX_ERRORCHECK 查錯(cuò)鎖:以損失些許性能的方式返回錯(cuò)誤信息;
PTHREAD_MUTEX_RECURSIVE就是遞歸鎖;

可以通過pthread_mutexattr_t屬性設(shè)置鎖的類型,示例代碼如下:

- (void)pthread_mutex_recursiveDemo {
    
    // init attr
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
//    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);

    // init mutex
    __block pthread_mutex_t mutex_recursive;
    pthread_mutex_init(&mutex_recursive, &attr);
    pthread_mutexattr_destroy(&attr);
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    
        static void (^RecursiveMethod)(int);
        RecursiveMethod = ^(int value) {
            // lock
            pthread_mutex_lock(&mutex_recursive);
            if (value > 0) {
                NSLog(@"value = %d,thread = %@",value,[NSThread currentThread]);
                RecursiveMethod(value - 1);
            }else{
                pthread_mutex_destroy(&mutex_recursive);
            }
            // unlock
            pthread_mutex_unlock(&mutex_recursive);
        };
        
        RecursiveMethod(5);
    });
    //    使用完畢后,銷毀
    //    pthread_mutex_destroy(& mutex_recursive);
}

// 打印結(jié)果:
2021-02-27 21:18:33.418542+0800 lockDemo[83366:6460107] value = 5,thread = <NSThread: 0x6000037bd380>{number = 6, name = (null)}
2021-02-27 21:18:33.418716+0800 lockDemo[83366:6460107] value = 4,thread = <NSThread: 0x6000037bd380>{number = 6, name = (null)}
2021-02-27 21:18:33.418845+0800 lockDemo[83366:6460107] value = 3,thread = <NSThread: 0x6000037bd380>{number = 6, name = (null)}
2021-02-27 21:18:33.419116+0800 lockDemo[83366:6460107] value = 2,thread = <NSThread: 0x6000037bd380>{number = 6, name = (null)}
2021-02-27 21:18:33.419250+0800 lockDemo[83366:6460107] value = 1,thread = <NSThread: 0x6000037bd380>{number = 6, name = (null)}

注意: pthread_mutex(recursive)只保證在單線程情況下可重入,當(dāng)多個(gè)線程獲取相同的pthread_mutex(recursive)鎖會(huì)導(dǎo)致死鎖的發(fā)生。

pthread_rwlock(讀寫鎖)

基本上所有的問題都可以用互斥的方案去解決,但是可以解決并不代表適合。

pthread_mutex(互斥鎖)有個(gè)缺點(diǎn),就是只要鎖住了,不管其他線程要干什么,都不允許進(jìn)入臨界區(qū)。設(shè)想這樣一種情況:臨界區(qū)變量a正在被線程1讀取,加了個(gè)mutex鎖,線程2如果也要讀變量a,因?yàn)楸痪€程1加了個(gè)互斥鎖,就只能等待線程1讀取完畢。但事實(shí)情況是,讀取數(shù)據(jù)并不影響數(shù)據(jù)內(nèi)容本身,所以即便被1個(gè)線程讀著,另外一個(gè)線程也應(yīng)該被允許去讀。除非另外一個(gè)線程是寫操作,為了避免數(shù)據(jù)不一致的問題,寫線程就需要等讀線程都結(jié)束了再寫。

因此誕生了讀寫鎖,有的地方也叫共享-獨(dú)占鎖

讀寫鎖的特性是這樣的,當(dāng)一個(gè)線程加了讀鎖訪問臨界區(qū),另外一個(gè)線程也想訪問臨界區(qū)讀取數(shù)據(jù)的時(shí)候,也可以加一個(gè)讀鎖,這樣另外一個(gè)線程就能夠成功進(jìn)入臨界區(qū)進(jìn)行讀操作了。此時(shí)讀鎖線程有兩個(gè)。當(dāng)?shù)谌齻€(gè)線程需要進(jìn)行寫操作時(shí),它需要加一個(gè)寫鎖,這個(gè)寫鎖只有在讀鎖的擁有者為0時(shí)才有效。也就是等前兩個(gè)讀線程都釋放讀鎖之后,第三個(gè)線程就能進(jìn)去寫了。總結(jié)一下就是:

  • 當(dāng)讀寫鎖被一個(gè)線程以讀模式占用的時(shí)候,寫操作的其他線程會(huì)被阻塞,讀操作的其他線程還可以繼續(xù)進(jìn)行。
  • 當(dāng)讀寫鎖被一個(gè)線程以寫模式占用的時(shí)候,寫操作的其他線程會(huì)被阻塞,讀操作的其他線程也被阻塞。

這樣更精細(xì)的控制,就能減少mutex導(dǎo)致的阻塞延遲時(shí)間。如果受保護(hù)的數(shù)據(jù)結(jié)構(gòu)經(jīng)常被讀取,并且只偶爾修改,則可以顯著提高性能。雖然用mutex也能起作用,但這種場合,明顯讀寫鎖更好。

pthread中讀寫鎖主要函數(shù)如下:

// 靜態(tài)初始化方法
PTHREAD_RWLOCK_INITIALIZER
// 動(dòng)態(tài)初始化,可傳pthread_rwlockattr_t屬性
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
// 銷毀 pthread_rwlock
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
// 獲得讀鎖
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
// 嘗試獲得讀鎖
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
// 獲得寫鎖
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
// 嘗試獲得寫鎖
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
// 釋放鎖
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

使用讀寫鎖與pthread_mutex類似,都是通過初始化創(chuàng)建鎖,之后根據(jù)讀寫不同場景進(jìn)行加鎖、解鎖操作,在使用完畢后別忘了銷毀鎖。示例代碼如下:

- (void)pthread_rwlock_demo {
    pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
    _rwlock = rwlock;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 讀
        [self readWithTag:1];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 讀
        [self readWithTag:2];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 寫
        [self writeWithTag:3];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 寫
        [self writeWithTag:4];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 讀
        [self readWithTag:5];
    });
    //使用完畢后銷毀鎖:不可在未使用完畢前銷毀
    //pthread_rwlock_destroy(&_rwlock);
}

- (void)readWithTag:(NSInteger )tag {
    pthread_rwlock_rdlock(&_rwlock);
    NSLog(@"start read ---- %ld",tag);
    self.path = [[NSBundle mainBundle] pathForResource:@"pthread_rwlock" ofType:@".txt"];
    self.content = [NSString stringWithContentsOfFile:self.path encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"end   read ---- %ld",tag);
    pthread_rwlock_unlock(&_rwlock);
}

- (void) writeWithTag:(NSInteger)tag {
    pthread_rwlock_wrlock(&_rwlock);
    NSLog(@"start wirte ---- %ld",tag);
    [self.content writeToFile:self.path atomically:YES encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"end   wirte ---- %ld",tag);
    pthread_rwlock_unlock(&_rwlock);
}

// 打印結(jié)果 :  讀操作可共享,寫操作互斥
2021-02-27 21:29:44.081500+0800 lockDemo[82462:10201536] start read ---- 2
2021-02-27 21:29:44.081500+0800 lockDemo[82462:10201541] start read ---- 1
2021-02-27 21:29:44.081795+0800 lockDemo[82462:10201536] end   read ---- 2
2021-02-27 21:29:44.081795+0800 lockDemo[82462:10201541] end   read ---- 1
2021-02-27 21:29:44.082017+0800 lockDemo[82462:10201537] start wirte ---- 3
2021-02-27 21:29:44.082182+0800 lockDemo[82462:10201537] end   wirte ---- 3
2021-02-27 21:29:44.082351+0800 lockDemo[82462:10201535] start wirte ---- 4
2021-02-27 21:29:44.082459+0800 lockDemo[82462:10201535] end   wirte ---- 4
2021-02-27 21:29:44.082617+0800 lockDemo[82462:10201538] start read ---- 5
2021-02-27 21:29:44.082799+0800 lockDemo[82462:10201538] end   read ---- 5

注意事項(xiàng)

避免寫線程饑餓
由于讀寫鎖的性質(zhì),在默認(rèn)情況下是很容易出現(xiàn)寫線程饑餓的。因?yàn)樗仨氁鹊剿凶x鎖都釋放之后,才能成功申請寫鎖。比如在寫線程阻塞的時(shí)候,有很多讀線程是可以一個(gè)接一個(gè)地在那兒插隊(duì)的(在默認(rèn)情況下,只要有讀鎖在,寫鎖就無法申請,然而讀鎖可以一直申請成功,就導(dǎo)致所謂的插隊(duì)現(xiàn)象),那么寫線程就不知道什么時(shí)候才能申請成功寫鎖了,然后它就餓死了。所以要注意鎖建立后的優(yōu)先級(jí)問題。不過不同系統(tǒng)的實(shí)現(xiàn)版本對寫線程的優(yōu)先級(jí)實(shí)現(xiàn)不同。Solaris下面就是寫線程優(yōu)先,其他系統(tǒng)默認(rèn)讀線程優(yōu)先。

pthread_cond (條件變量)

當(dāng)我們在使用多線程的時(shí)候,有時(shí)一把只會(huì)lock和unlock的鎖未必就能完全滿足我們的使用。因?yàn)槠胀ǖ逆i只能關(guān)心鎖與不鎖,而不在乎用什么鑰匙(滿足什么條件)才能開鎖,而我們在處理資源共享的時(shí)候,有時(shí)候需要只有滿足一定條件的情況下才能打開這把鎖。

這時(shí)候,POSIX提供的pthread_cond(條件變量)就派上了用場。主要的函數(shù)如下:

// 靜態(tài)初始化
PTHREAD_COND_INITIALIZER
// 動(dòng)態(tài)初始化并允許設(shè)置屬性
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
// 銷毀條件變量
int pthread_cond_destroy(pthread_cond_t *cond);
// 發(fā)送信號(hào)(給指定線程)
int pthread_cond_signal(pthread_cond_t *cond);
// 廣播信號(hào)(給所有線程)
int pthread_cond_broadcast(pthread_cond_t *cond);
// 等待信號(hào)
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
// 等待信號(hào),如果在指定時(shí)間仍未收到信號(hào),則返回
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mute

條件變量可以做到讓一個(gè)線程等待多個(gè)線程的結(jié)束,并在合適的時(shí)候喚醒正在等待的線程,具體是什么時(shí)候,取決于你設(shè)置的條件是否滿足。

示例代碼如下:

pthread_mutex_t mutex;
pthread_cond_t condition;
Boolean        ready_to_go = false;
 
void MyCondInitFunction()
{
    mutex =  (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_init(&condition, NULL);
}
 
void MyWaitOnConditionFunction()
{
    // Lock the mutex.
    pthread_mutex_lock(&mutex);
    // If the predicate is already set, then the while loop is bypassed;
    // otherwise, the thread sleeps until the predicate is set.
    while(ready_to_go == false)
    {
        pthread_cond_wait(&condition, &mutex);
    }
    
    // Do work. (The mutex should stay locked.)
   
    // Reset the predicate and release the mutex.
    ready_to_go = false;
    pthread_mutex_unlock(&mutex);
}

void SignalThreadUsingCondition()
{
    // At this point, there should be work for the other thread to do.
    pthread_mutex_lock(&mutex);
    
    ready_to_go = true;
    // Signal the other thread to begin work.
    pthread_cond_signal(&condition);
    pthread_mutex_unlock(&mutex);
}

- (void)pthread_cont_demo {
    MyCondInitFunction();
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        MyWaitOnConditionFunction();
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(1);
        SignalThreadUsingCondition();
    });
}

補(bǔ)充一下,原則上pthread_cond_signal是只通知一個(gè)線程,pthread_cond_broadcast是用于通知很多線程。但POSIX標(biāo)準(zhǔn)也允許讓pthread_cond_signal用于通知多個(gè)線程,不強(qiáng)制要求只允許通知一個(gè)線程。具體看各系統(tǒng)的實(shí)現(xiàn)。

另外,在調(diào)用pthread_cond_wait之前,必須要申請互斥鎖,當(dāng)線程通過pthread_cond_wait進(jìn)入waiting狀態(tài)時(shí),會(huì)釋放傳入的互斥鎖。

NSLock (互斥鎖)

NSLockCocoa 基于pthread_mutex實(shí)現(xiàn)的一個(gè)基本的互斥鎖。對應(yīng)pthread_mutex的PTHREAD_MUTEX_ERRORCHECK的類型。遵循NSLocking協(xié)議,該協(xié)議定義了lock和unlock方法。通過lockunlock來進(jìn)行鎖定和解鎖。

實(shí)際上,OC層面的基于pthread_mutex封裝的鎖對象都遵循NSLocking協(xié)議,這樣設(shè)計(jì)的目的是因?yàn)?,對于這些鎖的鎖定與解鎖行為對于底層的操作是一致的。使用這些方法來獲取和釋放鎖,就像使用任何pthread_mutex一樣。

除了NSLocking協(xié)議提供的標(biāo)準(zhǔn)鎖定行為,NSLock類還添加了tryLocklockBeforeDate:方法。

  • tryLock方法嘗試獲取該鎖,但如果該鎖不可用,并不會(huì)阻塞,該方法只返回NO。

  • lockBeforeDate:方法嘗試獲取鎖,但是如果在指定Date的時(shí)間限制內(nèi)沒有獲得鎖,則會(huì)解除線程阻塞(并返回NO)。

使用示例如下:

- (void)nslock_demo {
    //主線程
    NSLock *lock = [[NSLock alloc] init];
    //線程1
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [lock lock];
        NSLog(@"線程1任務(wù) 開始");
        sleep(2);
        NSLog(@"線程1任務(wù) 結(jié)束");
        [lock unlock];
    });
    //線程2
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(1);
        if ([lock tryLock]) {//嘗試獲取鎖,如果獲取不到返回NO,不會(huì)阻塞該線程
            NSLog(@"線程2嘗試獲取鎖,鎖可用");
            [lock unlock];
        }else{
            NSLog(@"線程2嘗試獲取鎖,鎖不可用");
        }
        
        NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:3];
        if ([lock lockBeforeDate:date]) {//嘗試在未來的3s內(nèi)獲取鎖,并阻塞該線程,如果3s內(nèi)獲取不到恢復(fù)線程, 返回NO,不會(huì)阻塞該線程
            NSLog(@"沒有超時(shí),線程2獲得鎖");
            [lock unlock];
        }else{
            NSLog(@"超時(shí),線程2沒有獲得鎖");
        }
    });
}

// 打印結(jié)果:
2021-02-27 21:44:10.071157+0800 lockDemo[36464:983765] 線程1任務(wù) 開始
2021-02-27 21:44:11.074331+0800 lockDemo[36464:983761] 線程2嘗試獲取鎖,鎖不可用
2021-02-27 21:44:12.074832+0800 lockDemo[36464:983765] 線程1任務(wù) 結(jié)束
2021-02-27 21:44:12.075065+0800 lockDemo[36464:983761] 沒有超時(shí),線程2獲得鎖

NSRecursiveLock (遞歸鎖)

NSRecursiveLock是Cocoa對pthread_mutex互斥鎖 PTHREAD_MUTEX_RECURSIVE類型的封裝。與pthread_mutex(遞歸鎖)一樣,主要是用在循環(huán)或遞歸操作中。該鎖可以被同一個(gè)線程多次獲取,而不會(huì)被阻塞。它記錄了成功獲得鎖的次數(shù),每一次成功的獲得鎖,都必須有一個(gè)配套的釋放鎖與其對應(yīng),只有當(dāng)所有的加鎖和解鎖調(diào)用都被平衡后,鎖才會(huì)被實(shí)際釋放,以便其他線程能夠獲取它。

除了實(shí)現(xiàn)NSLocking協(xié)議的方法外,NSRecursiveLock還提供了兩個(gè)方法,分別如下:

// 在給定的時(shí)間之前去嘗試請求一個(gè)鎖
- (BOOL)lockBeforeDate:(NSDate *)limit

// 嘗試去請求一個(gè)鎖,并會(huì)立即返回一個(gè)布爾值,表示嘗試是否成功
- (BOOL)tryLock

使用示例如下:

- (void)NSRecursiveLock_demo {
    //主線程
    NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
    //線程1
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        static void(^MyRecursiveFunction)(int);
        MyRecursiveFunction = ^(int value)
        {
            [recursiveLock lock];
            if (value > 0)
            {
                NSLog(@"遞歸任務(wù)1--%d",value);
                sleep(2);
                --value;
                MyRecursiveFunction(value);
            }
            [recursiveLock unlock];
        };
        MyRecursiveFunction(5);
    });
    //線程2
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(1);
        [recursiveLock lock];
        NSLog(@"任務(wù)2");
        [recursiveLock unlock];
    });
}

// 打印結(jié)果如下:
2021-02-27 21:48:33.853605+0800 lockDemo[83298:10293307] 遞歸任務(wù)1--5
2021-02-27 21:48:35.856179+0800 lockDemo[83298:10293307] 遞歸任務(wù)1--4
2021-02-27 21:48:37.859868+0800 lockDemo[83298:10293307] 遞歸任務(wù)1--3
2021-02-27 21:48:39.863572+0800 lockDemo[83298:10293307] 遞歸任務(wù)1--2
2021-02-27 21:48:41.868646+0800 lockDemo[83298:10293307] 遞歸任務(wù)1--1
2021-02-27 21:48:43.870858+0800 lockDemo[83298:10293303] 任務(wù)2

注意:由于遞歸鎖只有在所有鎖操作與解鎖操作得到平衡后才會(huì)被釋放,長時(shí)間持有任何鎖會(huì)導(dǎo)致其他線程阻塞,直到遞歸完成。如果可以通過重寫代碼來消除遞歸,或者消除使用遞歸鎖的需要,那么可能會(huì)獲得更好的性能。

NSCondition (條件)

NSCondition是對POSIX條件pthread_cond的封裝, 它將所需的鎖和條件數(shù)據(jù)結(jié)構(gòu)包裝在一個(gè)對象中。使得開發(fā)者可以像鎖定互斥鎖一樣鎖定它,然后像等待條件一樣等待它。

NSConditionNSLock、@synchronized等是不同的是,NSCondition可以給每個(gè)線程分別加鎖,加鎖后不影響其他線程進(jìn)入臨界區(qū)。其它線程也能上鎖,而之后可以根據(jù)條件決定是否繼續(xù)運(yùn)行線程,即線程是否要進(jìn)入 waiting 狀態(tài).

除了實(shí)現(xiàn)NSLocking協(xié)議的方法外,NSCondition還提供了以下函數(shù):

- (void)wait;   // 等待信號(hào)
- (BOOL)waitUntilDate:(NSDate *)limit;  // 等待信號(hào),如果limit時(shí)間已到,則直接返回
- (void)signal; // 發(fā)送信號(hào)
- (void)broadcast; // 廣播信號(hào)

通過NSCondition可以實(shí)現(xiàn)不同線程的調(diào)度。一個(gè)線程被某一個(gè)條件所阻塞,直到另一個(gè)線程滿足該條件從而發(fā)送信號(hào)給該線程使得該線程可以正確的執(zhí)行。

- (void)NSCondition_demo {
    __block NSInteger timeToDoWork = 0;
    NSCondition *cocoaCondition = [[NSCondition alloc] init];
    // 線程1
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [cocoaCondition lock];
        while (timeToDoWork <= 0){
            [cocoaCondition wait];
        }
         
        timeToDoWork--;
         
        // Do real work here.
         
        [cocoaCondition unlock];
    });
    
    // 線程2
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        [cocoaCondition lock];
        timeToDoWork++;
        [cocoaCondition signal];
        [cocoaCondition unlock];
    });
}

NSConditionLock (條件鎖)

NSConditionLock是對NSCondition的進(jìn)一步封裝,條件鎖對象所定義的互斥鎖可以用特定的值(某個(gè)條件)鎖定和解鎖。除了NSLocking協(xié)議外,NSConditionLock還提供如下函數(shù)與屬性:

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

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;  // 當(dāng)condition的值滿足條件時(shí) 獲取鎖
- (BOOL)tryLock; // 嘗試獲得鎖,不管是否獲得成功都立即返回,不阻塞線程
- (BOOL)tryLockWhenCondition:(NSInteger)condition; // 當(dāng)condition的值滿足條件時(shí),嘗試加鎖
- (void)unlockWithCondition:(NSInteger)condition; // 釋放鎖,并將condition的值修改為執(zhí)行值
- (BOOL)lockBeforeDate:(NSDate *)limit; // 在指定時(shí)間限制內(nèi)獲取鎖,獲取失敗,返回NO
// 在指定時(shí)間內(nèi),當(dāng)condition的值滿足條件時(shí)獲取鎖
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

通常,當(dāng)線程需要以特定的順序執(zhí)行任務(wù)時(shí),比如當(dāng)一個(gè)線程生產(chǎn)數(shù)據(jù)另一個(gè)線程消耗數(shù)據(jù)時(shí),可以使用NSConditionLock對象。在生產(chǎn)者執(zhí)行時(shí),可以通過特定的條件獲得鎖(條件本身只是定義的一個(gè)整數(shù)值),當(dāng)生產(chǎn)者完成時(shí),它將解鎖,并將鎖的條件設(shè)置為可以喚醒消費(fèi)者線程的條件。

下面的示例展示了如何使用條件鎖處理生產(chǎn)者-消費(fèi)者問題。假設(shè)一個(gè)應(yīng)用程序包含一個(gè)數(shù)據(jù)隊(duì)列。生產(chǎn)者線程向隊(duì)列中添加數(shù)據(jù),消費(fèi)者線程從隊(duì)列中提取數(shù)據(jù)。生成器不需要等待特定的條件,但是它必須等待鎖可用,這樣它才能安全地將數(shù)據(jù)添加到隊(duì)列中。

NSMutableArray *products = [NSMutableArray array];
NSConditionLock *lock = [[NSConditionLock alloc] init];
NSInteger HAS_DATA = 1;
NSInteger NO_DATA = 0;
    
dispatch_async(dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{
       
    while (1) {
        [lock lockWhenCondition:NO_DATA];
        [products addObject:[[NSObject alloc] init]];
        NSLog(@"produce a product, 總量: %zi", products.count);
        [lock unlockWithCondition:HAS_DATA];
        sleep(1);
    }
});
    
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
    while (1) {
        NSLog(@"wait for product");
        [lock lockWhenCondition:HAS_DATA];
        [products removeObjectAtIndex:0];
        NSLog(@"custome a product");
        [lock unlockWithCondition:NO_DATA];
    }
});

當(dāng)生產(chǎn)者釋放鎖的時(shí)候,把條件設(shè)置成了1。這樣消費(fèi)者可以獲得該鎖,進(jìn)而執(zhí)行程序,如果消費(fèi)者獲得鎖的條件和生產(chǎn)者釋放鎖時(shí)給定的條件不一致,則消費(fèi)者永遠(yuǎn)無法獲得鎖,也不能執(zhí)行程序。同樣,如果消費(fèi)者釋放鎖給定的條件和生產(chǎn)者獲得鎖給定的條件不一致的話,則生產(chǎn)者也無法獲得鎖,程序也不能執(zhí)行。

注意

  1. unlock 與 unlockWithCondition:(NSInteger)condition 的區(qū)別:
    • unlock:釋放鎖但并不改變condition的值;
    • unlockWithCondition:釋放鎖,并將condition的值修改為指定值。
  2. 由于在實(shí)現(xiàn)操作系統(tǒng)時(shí)的細(xì)微參與,即使代碼里沒有實(shí)際發(fā)出信號(hào),條件鎖也允許以虛假的成功返回。為了避免由這些假信號(hào)引起的問題,您應(yīng)該始終將謂詞與條件鎖結(jié)合使用。謂詞是確定線程繼續(xù)執(zhí)行是否安全的更具體的方法。這個(gè)條件只是讓線程處于休眠狀態(tài),直到發(fā)送信號(hào)的線程可以設(shè)置謂詞。

@sychronized

@sychronized是使用起來最簡單的互斥鎖,通常只需要@sychronized(obj)這樣一個(gè)簡單的指令就可以實(shí)現(xiàn)加/解鎖操作。

- (void)sychronized_demo {
    NSObject *obj = [[NSObject alloc] init];
    NSObject *obj1 = [[NSObject alloc] init];
    //線程1
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        @synchronized(obj){
            NSLog(@"任務(wù)1");
            sleep(5);
        }
    });
    //線程2
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        @synchronized(obj){
            NSLog(@"任務(wù)2");
        }
    });
}
// 打印結(jié)果:
2021-02-27 22:08:25.288126+0800 lockDemo[83702:10333558] 任務(wù)1
2021-02-27 22:08:30.291985+0800 lockDemo[83702:10333557] 任務(wù)2

@synchronized指令使用傳入的對象(obj)作為該鎖的唯一標(biāo)識(shí),只有當(dāng)標(biāo)識(shí)相同時(shí),才為滿足互斥,如果線程2中的@synchronized(obj)改為@synchronized(obj1),線程2就不會(huì)被阻塞。

// 如果將線程2的 @synchronized(obj)換成 @synchronized(obj1),則
2021-02-27 22:09:42.831004+0800 lockDemo[83783:10344549] 任務(wù)1
2021-02-27 22:09:42.831014+0800 lockDemo[83783:10344546] 任務(wù)2

同時(shí)@synchronized還允許重入,前面提到的pthread_mutex(遞歸鎖)和NSRecursiveLock也支持重入,但它們只允許在同一線程內(nèi)多次重入,而@synchronized支持多線程重入。這是因?yàn)?code>@sychronized內(nèi)部,除了維護(hù)了同一線程的加鎖次數(shù)lockCount外,還維護(hù)了使用唯一標(biāo)識(shí)的線程數(shù)threadCount。

@synchronized指令實(shí)現(xiàn)鎖的優(yōu)點(diǎn)就是我們不需要在代碼中顯式的創(chuàng)建鎖對象,便可以實(shí)現(xiàn)鎖的機(jī)制,但作為一種預(yù)防措施,@synchronized塊會(huì)隱式的添加一個(gè)異常處理例程來保護(hù)代碼,該處理例程會(huì)在異常拋出的時(shí)候自動(dòng)的釋放互斥鎖。所以如果不想讓隱式的異常處理例程帶來額外的開銷,你可以考慮使用鎖對象。

注意:確保傳入@synchronized的obj不為nil,因?yàn)槿绻麄魅氲膐bj為nil的話,實(shí)際上并不會(huì)做任何實(shí)際的內(nèi)容,也無法達(dá)到加鎖的目的。

dispatch_semaphore信號(hào)量

dispatch_semaphoreNSCondition類似,都是一種基于信號(hào)的同步方式,但NSCondition信號(hào)只能發(fā)送,不能保存(如果沒有線程在等待,則發(fā)送的信號(hào)會(huì)失效)。而 dispatch_semaphore能保存發(fā)送的信號(hào)。dispatch_semaphore 的核心是 dispatch_semaphore_t 類型的信號(hào)量。

dispatch_semaphore是信號(hào)量,但當(dāng)信號(hào)總量設(shè)為 1 時(shí)也可以當(dāng)作鎖來。在沒有等待情況出現(xiàn)時(shí),它的性能比 pthread_mutex 還要高,但一旦有等待情況出現(xiàn)時(shí),性能就會(huì)下降許多。相對于 OSSpinLock 來說,它的優(yōu)勢在于等待時(shí)不會(huì)消耗 CPU 資源。

與其相關(guān)的主要有三個(gè)函數(shù):

  • dispatch_semaphore_t dispatch_semaphore_create(long value)
    輸出一個(gè)dispatch_semaphore_t類型且值為value的信號(hào)量。值得注意的是,這里的傳入的參數(shù)value必須大于或等于0,否則dispatch_semaphore_create會(huì)返回NULL。

  • long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
    這個(gè)函數(shù)會(huì)使傳入的信號(hào)量dsema的值加1;

  • long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
    這個(gè)函數(shù)會(huì)使傳入的信號(hào)量dsema的值減1;
    這個(gè)函數(shù)的作用是這樣的,如果dsema信號(hào)量的值大于0,該函數(shù)所處線程就繼續(xù)執(zhí)行下面的語句,并且將信號(hào)量的值減1;如果desema的值為0,那么這個(gè)函數(shù)就阻塞當(dāng)前線程等待timeout(注意timeout的類型為dispatch_time_t,不能直接傳入整形或float型數(shù)),如果等待的期間desema的值被dispatch_semaphore_signal函數(shù)加1了,且該函數(shù)(即dispatch_semaphore_wait)所處線程獲得了信號(hào)量,那么就繼續(xù)向下執(zhí)行并將信號(hào)量減1。如果等待期間沒有獲取到信號(hào)量或者信號(hào)量的值一直為0,那么等到timeout時(shí),其所處線程自動(dòng)執(zhí)行其后語句。

示例代碼如下:

dispatch_semaphore_t signal = dispatch_semaphore_create(1);
    dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(signal, overTime);
            NSLog(@"需要線程同步的操作1 開始");
            sleep(2);
            NSLog(@"需要線程同步的操作1 結(jié)束");
        dispatch_semaphore_signal(signal);
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        dispatch_semaphore_wait(signal, overTime);
            NSLog(@"需要線程同步的操作2");
        dispatch_semaphore_signal(signal);
    });
    
//執(zhí)行結(jié)果為:
需要線程同步的操作1 開始
需要線程同步的操作1 結(jié)束
需要線程同步的操作2

OSSpinLock 自旋鎖

OSSpinLock是一把自旋鎖,性能很高。因?yàn)樗恢笔莇o while忙等狀態(tài)。這種自旋鎖的缺點(diǎn)是當(dāng)?shù)却龝r(shí)會(huì)消耗大量CPU資源,所以它不適用于較長時(shí)間的任務(wù)。

OSSpinLock是整數(shù)類型,約定是解鎖為零,鎖定為非零。鎖必須自然對齊,并且不能在緩存抑制的內(nèi)存中。

如果鎖已經(jīng)被持有,OSSpinLockLock()將自旋,但會(huì)使用各種各樣的策略來后退,使其對大多數(shù)優(yōu)先級(jí)反轉(zhuǎn)活鎖免疫。但因?yàn)樗梢孕D(zhuǎn),所以在某些情況下可能效率低下。

如果鎖被持有,OSSpinLockTry()立即返回false,如果它獲得了鎖,則返回true。它不自旋。 OSSpinLockUnlock()通過置零無條件地解鎖鎖。

OSSpinLock 示例

- (void)osspinlock_demo {
    __block OSSpinLock theLock = OS_SPINLOCK_INIT;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        OSSpinLockLock(&theLock);
        NSLog(@"線程1");
        sleep(5);
        OSSpinLockUnlock(&theLock);
        NSLog(@"線程1解鎖成功");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        OSSpinLockLock(&theLock);
        NSLog(@"線程2");
        OSSpinLockUnlock(&theLock);
    });
}
// 打印結(jié)果
2021-02-27 22:05:13.526 ThreadLockControlDemo[2856:316247] 線程1
2021-02-27 22:05:23.528 ThreadLockControlDemo[2856:316247] 線程1解鎖成功
2021-02-27 22:05:23.529 ThreadLockControlDemo[2856:316260] 線程2

OSSpinLock 問題

新版iOS中,系統(tǒng)維護(hù)了5個(gè)不同的線程優(yōu)先級(jí)/QoS: background,utility,default,user-initiated,user-interactive。高優(yōu)先級(jí)線程始終會(huì)在低優(yōu)先級(jí)線程前執(zhí)行,一個(gè)線程不會(huì)受到比它更低優(yōu)先級(jí)線程的干擾。這種線程調(diào)度算法會(huì)產(chǎn)生潛在的優(yōu)先級(jí)反轉(zhuǎn)問題,從而破壞了OSSpinLock。

具體來說,如果一個(gè)低優(yōu)先級(jí)的線程獲得鎖并訪問共享資源,這時(shí)一個(gè)高優(yōu)先級(jí)的線程也嘗試獲得這個(gè)鎖,它會(huì)處于OSSpinLock的忙等狀態(tài)從而占用大量 CPU。此時(shí)低優(yōu)先級(jí)線程無法與高優(yōu)先級(jí)線程爭奪 CPU 時(shí)間,從而導(dǎo)致任務(wù)遲遲完不成、無法釋放OSSpinLock。

所以從iOS10.0開始,蘋果棄用了OSSpinLock,并用os_unfair_lock進(jìn)行替代。不過,os_unfair_lock的實(shí)現(xiàn)屬于互斥鎖,當(dāng)鎖被占用的時(shí)候,線程處于阻塞狀態(tài),而非忙等。

iOS中的鎖的性能

在iOS中,各種鎖的性能如下圖所示:


16239819199365.jpg

從圖中可以看出,在iOS中的鎖性能從高往底依次是:

  • OSSpinLock(自旋鎖)
  • dispatch_semaphone(信號(hào)量)
  • pthread_mutex(互斥鎖)
  • NSLock(互斥鎖)
  • NSCondition(條件鎖)
  • pthread_mutex(recursive 互斥遞歸鎖)
  • NSRecursiveLock(遞歸鎖)
  • NSConditionLock(條件鎖)
  • synchronized(互斥鎖)

性能總結(jié):

  1. OSSpinLock自旋鎖由于安全性問題,在iOS10之后已經(jīng)被廢棄,其底層的實(shí)現(xiàn)用os_unfair_lock替代
    • 使用OSSpinLock會(huì)處于忙等待狀態(tài)
    • 使用os_unfair_lock會(huì)處于休眠狀態(tài)
  2. atomic原子鎖自帶一把自旋鎖,只能保證setter、getter時(shí)的線程安全,在日常開發(fā)中使用更多的還是nonatomic修飾屬性
    • atomic:當(dāng)屬性在調(diào)用setter、getter方法時(shí),會(huì)加上自旋鎖OSSpinLock,用于保證同一時(shí)刻只能有一個(gè)線程調(diào)用屬性的讀或?qū)懀苊饬藢傩宰x寫不同步的問題。由于是底層編譯器自動(dòng)生成的互斥鎖代碼,會(huì)導(dǎo)致效率相對較低
    • nonatomic:當(dāng)屬性在調(diào)用setter、getter方法時(shí),不會(huì)加上自旋鎖,即線程不安全。由于編譯器不會(huì)自動(dòng)生成互斥鎖代碼,可以提高效率
  3. @synchronized在底層維護(hù)了一個(gè)哈希表進(jìn)行線程data的存儲(chǔ),通過鏈表表示可重入(即嵌套)的特性,雖然性能較低,但由于簡單好用,使用頻率很高
  4. NSLock、NSRecursiveLock底層是對pthread_mutex的封裝
  5. NSConditionNSConditionLock是條件鎖,底層都是對pthread_mutex的封裝,當(dāng)滿足某一個(gè)條件時(shí)才能進(jìn)行操作,和信號(hào)量dispatch_semaphore類似
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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