ios開發中的幾種鎖(一)

版本記錄

版本號 時間
V1.0 2017.05.20

前言

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

詳情

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

幾種鎖的性能比較

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

一、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

看輸出結果

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解鎖了

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

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

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

讓我們看一下輸出結果

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解鎖了

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

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

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

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

  • 當前線程鎖失敗,也可以繼續其它任務,用 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,時間到后會執行其后的語句
    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   發送信號");
        
    });
    
    
    //線程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  發送信號");
        
    });
}

@end

下面看輸出結果

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  發送信號
2017-05-20 11:02:49.476 lock[2238:107200] num1=12
2017-05-20 11:02:49.478 lock[2238:107200] 線程1   發送信號

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

下面說一下信號量的幾個參數

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

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

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

我們再次修改代碼

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

我們在看一下輸出結果

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   發送信號
2017-05-20 11:14:52.268 lock[2422:116184] 線程2  發送信號

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

相關參考技術博客

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

后記

今天就寫了2種鎖,剩下的待續~~~

家鄉
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容

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