ios開發(fā)中的幾種鎖(一)

版本記錄

版本號 時間
V1.0 2017.05.20

前言

ios中有好幾種鎖,比如自旋鎖,互斥鎖,信號量等等,鎖其實是多線程數(shù)據(jù)安全的一種解決方案,作用就是保證同一時間只有一個線程訪問和改變某些敏感數(shù)據(jù),這些鎖的性能也是差別很大,最近看了幾個技術(shù)大牛的技術(shù)博客,我才發(fā)現(xiàn)我以前對鎖的理解太膚淺了,心虛的趕緊找資料又開始了深入學習,然后整理出來。
這篇主要講這幾種鎖的基本情況。

詳情

在說明幾種鎖的基本情況之前,我們先看看ios開發(fā)中這八種鎖的名稱和它們的性能,如下圖所示。

幾種鎖的性能比較

下面主要對這幾種鎖的使用簡單的進行說明。

一、OSSpinLock自旋鎖

OSSpinLock自旋鎖,它的性能相對是最高的,差不多是150us。下面我們直接上代碼。

1. JJOSSLockVC.h
#import <UIKit/UIKit.h>

@interface JJOSSLockVC : UIViewController

@end


2. JJOSSLockVC.m

#import "JJOSSLockVC.h"
#import "libkern/OSAtomic.h"

@interface JJOSSLockVC ()

@end

@implementation JJOSSLockVC

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    //自旋鎖
    [self aboutOssPinLock];
    
}

#pragma mark - Object Private Function

//自旋鎖
- (void)aboutOssPinLock
{
    __block OSSpinLock osslock = OS_SPINLOCK_INIT;
    NSInteger __block num = 10;
    
    //線程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        NSLog(@"線程1%@準備上鎖",[NSThread currentThread]);
        
        OSSpinLockLock(&osslock);
        NSLog(@"我是線程1%@",[NSThread currentThread]);
        num = num + 1;
        NSLog(@"num1=%ld",num);
        
        OSSpinLockUnlock(&osslock);
        NSLog(@"我是線程1解鎖了");
        
    });
    
    NSLog(@"---------分割線----------");
    
    //線程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        NSLog(@"線程2%@準備上鎖",[NSThread currentThread]);
        
        OSSpinLockLock(&osslock);
        NSLog(@"我是線程2%@",[NSThread currentThread]);
        num = num - 1;
        NSLog(@"num2=%ld",num);
        
        OSSpinLockUnlock(&osslock);
        NSLog(@"我是線程2解鎖了");
        
    });

}

@end

看輸出結(jié)果

2017-05-20 10:05:35.738 lock[1557:65798] 線程1<NSThread: 0x60000007bb80>{number = 3, name = (null)}準備上鎖
2017-05-20 10:05:35.738 lock[1557:65630] ---------分割線----------
2017-05-20 10:05:35.739 lock[1557:65798] 我是線程1<NSThread: 0x60000007bb80>{number = 3, name = (null)}
2017-05-20 10:05:35.739 lock[1557:65799] 線程2<NSThread: 0x608000076400>{number = 4, name = (null)}準備上鎖
2017-05-20 10:05:35.739 lock[1557:65798] num1=11
2017-05-20 10:05:35.740 lock[1557:65798] 我是線程1解鎖了
2017-05-20 10:05:35.740 lock[1557:65799] 我是線程2<NSThread: 0x608000076400>{number = 4, name = (null)}
2017-05-20 10:05:35.740 lock[1557:65799] num2=10
2017-05-20 10:05:35.740 lock[1557:65799] 我是線程2解鎖了

由輸出結(jié)果可知,num數(shù)據(jù)被鎖住了,不會因為兩個線程的訪問而導致數(shù)據(jù)不安全。可以發(fā)現(xiàn)當我們同時鎖上線程1和線程2的時候,線程2會一直等待(自旋鎖不會讓等待的進入睡眠狀態(tài)),直到線程1的任務(wù)執(zhí)行完且解鎖完畢,線程2才會執(zhí)行。

下面我們修改一下代碼,將線程1的解鎖代碼注釋掉

//注釋掉線程1的解鎖代碼
// OSSpinLockUnlock(&osslock);

讓我們看一下輸出結(jié)果

2017-05-20 10:27:56.603 lock[1832:82635] ---------分割線----------
2017-05-20 10:27:56.603 lock[1832:82692] 線程1<NSThread: 0x608000074240>{number = 3, name = (null)}準備上鎖
2017-05-20 10:27:56.604 lock[1832:82692] 我是線程1<NSThread: 0x608000074240>{number = 3, name = (null)}
2017-05-20 10:27:56.604 lock[1832:82693] 線程2<NSThread: 0x608000074000>{number = 4, name = (null)}準備上鎖
2017-05-20 10:27:56.604 lock[1832:82692] num1=11
2017-05-20 10:27:56.604 lock[1832:82692] 我是線程1解鎖了

由輸出結(jié)果可知,因為我們注釋掉了線程1中的解鎖代碼,會繞過線程1,直到調(diào)用了線程2的解鎖方法才會繼續(xù)執(zhí)行線程1中的任務(wù),正常情況下,lock和unlock最好成對出現(xiàn)。這里注釋掉了線程1的解鎖代碼,導致線程1無法解鎖,所以線程2里面的num2不會執(zhí)行和打印輸出。

這里面用到了幾個參數(shù),如下所示:

OS_SPINLOCK_INIT: 默認值為 0,在 locked 狀態(tài)時就會大于 0,unlocked狀態(tài)下為 0
OSSpinLockLock(&oslock):上鎖,參數(shù)為 OSSpinLock 地址
OSSpinLockUnlock(&oslock):解鎖,參數(shù)為 OSSpinLock 地址
OSSpinLockTry(&oslock):嘗試加鎖,可以加鎖則立即加鎖并返回 YES,反之返回 NO

這里還要說一下trylock和lock的區(qū)別,如下所示:

  • 當前線程鎖失敗,也可以繼續(xù)其它任務(wù),用 trylock 合適。
  • 當前線程只有鎖成功后,才會做一些有意義的工作,那就 lock,沒必要輪詢 trylock。

二、dispatch_semaphore 信號量

還是直接看代碼


1. JJSemaphoreLockVC.h
#import <UIKit/UIKit.h>

@interface JJSemaphoreLockVC : UIViewController

@end

2. JJSemaphoreLockVC.m

#import "JJSemaphoreLockVC.h"

@interface JJSemaphoreLockVC ()

@end

#pragma mark - Override Base Function

@implementation JJSemaphoreLockVC

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self aboutSemaphoreLock];
}

#pragma mark - Object Private Function

- (void)aboutSemaphoreLock
{
    //傳入值必須 >=0, 若傳入為0則阻塞線程并等待timeout,時間到后會執(zhí)行其后的語句
    dispatch_semaphore_t signal = dispatch_semaphore_create(1);
    dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);
    NSInteger __block num = 10;
    
    //線程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        NSLog(@"我是線程1%@,等待中",[NSThread currentThread]);
        
        //signal值 -1
        dispatch_semaphore_wait(signal, overTime);
        num = num + 1;
        NSLog(@"num1=%ld",num);
        
        //signal值 +1
        dispatch_semaphore_signal(signal);
        NSLog(@"線程1   發(fā)送信號");
        
    });
    
    
    //線程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        NSLog(@"我是線程2%@,等待中",[NSThread currentThread]);
        
        //signal值 -1
        dispatch_semaphore_wait(signal, overTime);
        num = num + 1;
        NSLog(@"num2=%ld",num);
        
        //signal值 +1
        dispatch_semaphore_signal(signal);
        NSLog(@"線程2  發(fā)送信號");
        
    });
}

@end

下面看輸出結(jié)果

2017-05-20 11:02:49.473 lock[2238:107199] 我是線程2<NSThread: 0x60000026c5c0>{number = 4, name = (null)},等待中
2017-05-20 11:02:49.473 lock[2238:107200] 我是線程1<NSThread: 0x6080002640c0>{number = 3, name = (null)},等待中
2017-05-20 11:02:49.474 lock[2238:107199] num2=11
2017-05-20 11:02:49.476 lock[2238:107199] 線程2  發(fā)送信號
2017-05-20 11:02:49.476 lock[2238:107200] num1=12
2017-05-20 11:02:49.478 lock[2238:107200] 線程1   發(fā)送信號

由上發(fā)現(xiàn),因為我們初始化信號量的時候是大于 0 的,所以并沒有阻塞線程,而是直接執(zhí)行了線程1和線程2。

下面說一下信號量的幾個參數(shù)

dispatch_semaphore_create(1): 傳入值必須 >=0, 若傳入為 0 則阻塞線程并等待timeout,時間到后會執(zhí)行其后的語句
dispatch_semaphore_wait(signal, overTime):可以理解為 lock,會使得 signal 值 -1
dispatch_semaphore_signal(signal):可以理解為 unlock,會使得 signal 值 +1

下面有個比較形象的比喻,是我在別的博客上看到的,寫的不錯。

停車場剩余4個車位,那么即使同時來了四輛車也能停的下。如果此時來了五輛車,那么就有一輛需要等待。
信號量的值(signal): 它就相當于剩余車位的數(shù)目,dispatch_semaphore_wait 函數(shù)就相當于來了一輛車,dispatch_semaphore_signal 就相當于走了一輛車。停車位的剩余數(shù)目在初始化的時候就已經(jīng)指明了(dispatch_semaphore_create(long value)),調(diào)用一次 dispatch_semaphore_signal,剩余的車位就增加一個;調(diào)用一次dispatch_semaphore_wait 剩余車位就減少一個;當剩余車位為 0 時,再來車(即調(diào)用 dispatch_semaphore_wait)就只能等待。有可能同時有幾輛車等待一個停車位。有些車主沒有耐心,給自己設(shè)定了一段等待時間,這段時間內(nèi)等不到停車位就走了,如果等到了就開進去停車。而有些車主就像把車停在這,所以就一直等下去。

我們再次修改代碼

//dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_semaphore_t signal = dispatch_semaphore_create(0);

我們在看一下輸出結(jié)果

2017-05-20 11:14:49.192 lock[2422:116184] 我是線程2<NSThread: 0x60800006ef40>{number = 4, name = (null)},等待中
2017-05-20 11:14:49.192 lock[2422:116201] 我是線程1<NSThread: 0x60800006f000>{number = 3, name = (null)},等待中
2017-05-20 11:14:52.267 lock[2422:116201] num1=11
2017-05-20 11:14:52.267 lock[2422:116184] num2=12
2017-05-20 11:14:52.267 lock[2422:116201] 線程1   發(fā)送信號
2017-05-20 11:14:52.268 lock[2422:116184] 線程2  發(fā)送信號

這個主要是看時間戳,可以看見49~52,也就是說dispatch_semaphore_create(0)時,線程1和2里面的代碼輸出num1和num2的值要等3s才會執(zhí)行,而不會立即執(zhí)行。

相關(guān)參考技術(shù)博客

1.iOS 開發(fā)中的八種鎖(Lock)
2.不再安全的 OSSpinLock
3. NSRecursiveLock遞歸鎖的使用
4.關(guān)于dispatch_semaphore的使用
5.實現(xiàn)鎖的多種方式和鎖的高級用法

后記

今天就寫了2種鎖,剩下的待續(xù)~~~

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

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

  • 鎖是一種同步機制,用于多線程環(huán)境中對資源訪問的限制iOS中常見鎖的性能對比圖(摘自:ibireme): iOS鎖的...
    LiLS閱讀 1,631評論 0 6
  • 為什么要有鎖? 在使用多線程的時候多個線程可能會訪問同一塊資源,這樣就很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全等問題,這時候就...
    153037c65b0c閱讀 611評論 0 1
  • 一、線程鎖相關(guān)概念 線程鎖:我們在使用多線程的時候多個線程可能會訪問同一塊資源,這樣就很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全...
    2525252472閱讀 487評論 0 2
  • 自旋鎖和互斥鎖 共同點:都能保證同一時刻只能有一個線程操作鎖住的代碼。都能保證線程安全。不同點: 互斥鎖(mute...
    中軸線_lz閱讀 807評論 0 0
  • 許藝涵閱讀 238評論 0 0

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