版本記錄
版本號 | 時間 |
---|---|
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種鎖,剩下的待續~~~