iOS中的各種鎖

在日常開發(fā)過程中,為了提升程序運(yùn)行效率,以及用戶體驗,我們經(jīng)常使用多線程。在使用多線程的過程中,難免會遇到資源競爭問題。我們采用鎖的機(jī)制來確保線程安全。

線程安全

當(dāng)一個線程訪問數(shù)據(jù)的時候,其他的線程不能對其進(jìn)行訪問,直到該線程訪問完畢。即,同一時刻,對同一個數(shù)據(jù)操作的線程只有一個。只有確保了這樣,才能使數(shù)據(jù)不會被其他線程污染。而線程不安全,則是在同一時刻可以有多個線程對該數(shù)據(jù)進(jìn)行訪問,從而得不到預(yù)期的結(jié)果。

比如寫文件和讀文件,當(dāng)一個線程在寫文件的時候,理論上來說,如果這個時候另一個線程來直接讀取的話,那么得到將是不可預(yù)期的結(jié)果。

為了線程安全,我們可以使用鎖的機(jī)制來確保,同一時刻只有同一個線程來對同一個數(shù)據(jù)源進(jìn)行訪問。在開發(fā)過程中我們通常使用以下幾種鎖。

  1. NSLock
  2. NSRecursiveLock
  3. NSCondition
  4. NSConditionLock
  5. pthread_mutex
  6. pthread_rwlock
  7. POSIX Conditions
  8. OSSpinLock
  9. os_unfair_lock
  10. dispatch_semaphore
  11. @synchronized

信號量

在多線程環(huán)境下用來確保代碼不會被并發(fā)調(diào)用。在進(jìn)入一段代碼前,必須獲得一個信號量,在結(jié)束代碼前,必須釋放該信號量,其他想要想要執(zhí)行該代碼的線程必須等待直到前者釋放了該信號量。

以一個停車場的運(yùn)作為例。簡單起見,假設(shè)停車場只有三個車位,一開始三個車位都是空的。這時如果同時來了五輛車,看門人允許其中三輛直接進(jìn)入,然后放下車攔,剩下的車則必須在入口等待,此后來的車也都不得不在入口處等待。這時,有一輛車離開停車場,看門人得知后,打開車攔,放入外面的一輛進(jìn)去,如果又離開兩輛,則又可以放入兩輛,如此往復(fù)。
在這個停車場系統(tǒng)中,車位是公共資源,每輛車好比一個線程,看門人起的就是信號量的作用。

互斥鎖

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

NSLock

NSLock實(shí)現(xiàn)了最基本的互斥鎖,遵循了 NSLocking 協(xié)議,通過 lockunlock 來進(jìn)行鎖定和解鎖。其使用也非常簡單

- (void)doSomething {
   [self.lock lock];
   //TODO: do your stuff
   [self.lock unlock];
}

由于是互斥鎖,當(dāng)一個線程進(jìn)行訪問的時候,該線程獲得鎖,其他線程進(jìn)行訪問的時候,將被操作系統(tǒng)掛起,直到該線程釋放鎖,其他線程才能對其進(jìn)行訪問,從而卻確保了線程安全。但是如果連續(xù)鎖定兩次,則會造成死鎖問題。那如果想在遞歸中使用鎖,那要怎么辦呢,這就用到了 NSRecursiveLock 遞歸鎖。

NSRecursiveLock

遞歸鎖,顧名思義,可以被一個線程多次獲得,而不會引起死鎖。它記錄了成功獲得鎖的次數(shù),每一次成功的獲得鎖,必須有一個配套的釋放鎖和其對應(yīng),這樣才不會引起死鎖。只有當(dāng)所有的鎖被釋放之后,其他線程才可以獲得鎖

NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
 
void MyRecursiveFunction(int value)
{
    [theLock lock];
    if (value != 0)
    {
        --value;
        MyRecursiveFunction(value);
    }
    [theLock unlock];
}
 
MyRecursiveFunction(5);

NSCondition

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

- (void)download {
    [self.condition lock];
    //TODO: 下載文件代碼
    if (donloadFinish) { // 下載結(jié)束后,給另一個線程發(fā)送信號,喚起另一個處理程序
        [self.condition signal];
        [self.condition unlock];
    }
}

- (void)doStuffWithDownloadPicture {
    [self.condition lock];
    
    while (!donloadFinish) {
        [self.condition wait];
    }
    //TODO: 處理圖片代碼
    
    [self.condition unlock];
}

NSConditionLock

NSConditionLock 對象所定義的互斥鎖可以在使得在某個條件下進(jìn)行鎖定和解鎖。它和 NSCondition 很像,但實(shí)現(xiàn)方式是不同的。

當(dāng)兩個線程需要特定順序執(zhí)行的時候,例如生產(chǎn)者消費(fèi)者模型,則可以使用 NSConditionLock 。當(dāng)生產(chǎn)者執(zhí)行執(zhí)行的時候,消費(fèi)者可以通過特定的條件獲得鎖,當(dāng)生產(chǎn)者完成執(zhí)行的時候,它將解鎖該鎖,然后把鎖的條件設(shè)置成喚醒消費(fèi)者線程的條件。鎖定和解鎖的調(diào)用可以隨意組合,lockunlockWithCondition: 配合使用 lockWhenCondition:unlock 配合使用。

- (void)producer {
    while (YES) {
        [self.conditionLock lock];
        NSLog(@"have something");
        self.count++;
        [self.conditionLock unlockWithCondition:1];
    }
}

- (void)consumer {
    while (YES) {
        [self.conditionLock lockWhenCondition:1];
        NSLog(@"use something");
        self.count--;
        [self.conditionLock unlockWithCondition:0];
    }
}

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

pthread_mutex

POSIX 互斥鎖是一種超級易用的互斥鎖,使用的時候,只需要初始化一個 pthread_mutex_tpthread_mutex_lock 來鎖定 pthread_mutex_unlock 來解鎖,當(dāng)使用完成后,記得調(diào)用 pthread_mutex_destroy 來銷毀鎖。

    pthread_mutex_init(&lock,NULL);
    pthread_mutex_lock(&lock);
    //do your stuff
    pthread_mutex_unlock(&lock);
    pthread_mutex_destroy(&lock);

pthread_rwlock

讀寫鎖,在對文件進(jìn)行操作的時候,寫操作是排他的,一旦有多個線程對同一個文件進(jìn)行寫操作,后果不可估量,但讀是可以的,多個線程讀取時沒有問題的。

  • 當(dāng)讀寫鎖被一個線程以讀模式占用的時候,寫操作的其他線程會被阻塞,讀操作的其他線程還可以繼續(xù)進(jìn)行。
  • 當(dāng)讀寫鎖被一個線程以寫模式占用的時候,寫操作的其他線程會被阻塞,讀操作的其他線程也被阻塞。
// 初始化
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER
// 讀模式
pthread_rwlock_wrlock(&lock);
// 寫模式
pthread_rwlock_rdlock(&lock);
// 讀模式或者寫模式的解鎖
pthread_rwlock_unlock(&lock);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        [self readBookWithTag:1];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        [self readBookWithTag:2];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        [self writeBook:3];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        [self writeBook:4];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        [self readBookWithTag:5];
    });


- (void)readBookWithTag:(NSInteger )tag {
    pthread_rwlock_rdlock(&rwLock);
    NSLog(@"start read ---- %ld",tag);
    self.path = [[NSBundle mainBundle] pathForResource:@"1" ofType:@".doc"];
    self.contentString = [NSString stringWithContentsOfFile:self.path encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"end   read ---- %ld",tag);
    pthread_rwlock_unlock(&rwLock);
}

- (void)writeBook:(NSInteger)tag {
    pthread_rwlock_wrlock(&rwLock);
    NSLog(@"start wirte ---- %ld",tag);
    [self.contentString writeToFile:self.path atomically:YES encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"end   wirte ---- %ld",tag);
    pthread_rwlock_unlock(&rwLock);
}
start   read ---- 1
start   read ---- 2
end    read ---- 1
end    read ---- 2
start   wirte ---- 3
end    wirte ---- 3
start   wirte ---- 4
end    wirte ---- 4
start   read ---- 5
end    read ---- 5

POSIX Conditions

POSIX 條件鎖需要互斥鎖和條件兩項來實(shí)現(xiàn),雖然看起來沒什么關(guān)系,但在運(yùn)行時中,互斥鎖將會與條件結(jié)合起來。線程將被一個互斥和條件結(jié)合的信號來喚醒。

首先初始化條件和互斥鎖,當(dāng) ready_to_goflase 的時候,進(jìn)入循環(huán),然后線程將會被掛起,直到另一個線程將 ready_to_go 設(shè)置為 true 的時候,并且發(fā)送信號的時候,該線程會才被喚醒。

pthread_mutex_t mutex;
pthread_cond_t condition;
Boolean     ready_to_go = true;
 
void MyCondInitFunction()
{
    pthread_mutex_init(&mutex);
    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);
}

OSSpinLock

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

// 初始化
spinLock = OS_SPINLOCK_INIT;
// 加鎖
OSSpinLockLock(&spinLock);
// 解鎖
OSSpinLockUnlock(&spinLock);

然而,YYKit 作者 @ibireme 的文章也有說這個自旋鎖存在優(yōu)先級反轉(zhuǎn)問題,具體文章可以戳 不再安全的 OSSpinLock。

os_unfair_lock

自旋鎖已經(jīng)不在安全,然后蘋果又整出來個 os_unfair_lock_t (╯‵□′)╯︵┻━┻
這個鎖解決了優(yōu)先級反轉(zhuǎn)問題。

    os_unfair_lock_t unfairLock;
    unfairLock = &(OS_UNFAIR_LOCK_INIT);
    os_unfair_lock_lock(unfairLock);
    os_unfair_lock_unlock(unfairLock);

dispatch_semaphore

信號量機(jī)制實(shí)現(xiàn)鎖,等待信號,和發(fā)送信號,正如前邊所說的看門人一樣,當(dāng)有多個線程進(jìn)行訪問的時候,只要有一個獲得了信號,其他線程的就必須等待該信號釋放。

- (void)semphone:(NSInteger)tag {
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW);
    // do your stuff
    dispatch_semaphore_signal(semaphore);
}

@synchronized

一個便捷的創(chuàng)建互斥鎖的方式,它做了其他互斥鎖所做的所有的事情。

- (void)myMethod:(id)anObj
{
    @synchronized(anObj)
    {
        // Everything between the braces is protected by the @synchronized directive.
    }
}

如果你在不同的線程中傳過去的是一樣的標(biāo)識符,先獲得鎖的會鎖定代碼塊,另一個線程將被阻塞,如果傳遞的是不同的標(biāo)識符,則不會造成線程阻塞。

總結(jié)

應(yīng)當(dāng)針對不同的操作使用不同的鎖,而不能一概而論那種鎖的加鎖解鎖速度快。

  • 當(dāng)進(jìn)行文件讀寫的時候,使用 pthread_rwlock 較好,文件讀寫通常會消耗大量資源,而使用互斥鎖同時讀文件的時候會阻塞其他讀文件線程,而 pthread_rwlock 不會。
  • 當(dāng)性能要求較高時候,可以使用 pthread_mutex 或者 dispath_semaphore,由于 OSSpinLock 不能很好的保證線程安全,而在只有在 iOS10 中才有 os_unfair_lock ,所以,前兩個是比較好的選擇。既可以保證速度,又可以保證線程安全。
  • 對于 NSLock 及其子類,速度來說 NSLock < NSCondition < NSRecursiveLock < NSConditionLock 。

引用
1.Threading Programming Guide
2.百度百科-線程安全
3.百度百科-信號量
4.百度百科-互斥鎖
5.不再安全的 OSSpinLock

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

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

  • 在日常開發(fā)過程中,為了提升程序運(yùn)行效率,以及用戶體驗,我們經(jīng)常使用多線程。在使用多線程的過程中,難免會遇到資源競爭...
    星捷閱讀 933評論 0 0
  • 在日常開發(fā)過程中,為了提升程序運(yùn)行效率,以及用戶體驗,我們經(jīng)常使用多線程。在使用多線程的過程中,難免會遇到資源競爭...
    知識小集閱讀 6,003評論 8 61
  • 鎖是一種同步機(jī)制,用于多線程環(huán)境中對資源訪問的限制iOS中常見鎖的性能對比圖(摘自:ibireme): iOS鎖的...
    LiLS閱讀 1,630評論 0 6
  • 引用自多線程編程指南應(yīng)用程序里面多個線程的存在引發(fā)了多個執(zhí)行線程安全訪問資源的潛在問題。兩個線程同時修改同一資源有...
    Mitchell閱讀 2,119評論 1 7
  • 今天中午參加了CS Dept舉辦的Career Strategy類的小講座,講座的主題是關(guān)于個人理財Persona...
    可妞兒閱讀 1,221評論 1 0

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