題記:雖然有些事情的發(fā)生可能是你預料之中的,但是當它真正的發(fā)生了的時候,還是很難以接受的,還是需要一點時間,去緩和這種消極的情緒,盡快站起來吧!加油!花了一天半的時間,各種查閱資料,總結(jié)了iOS中關(guān)于線程鎖的知識,希望我能從中學到一些,也希望可以幫到同樣有需要的你!(文中如有錯誤,還請?zhí)岢觯黄鸾涣鳎?/em>
本文主要介紹:
- 互斥鎖
- 遞歸鎖
- 讀寫鎖
- 自旋鎖
- 分布鎖
- 條件變量
- 信號量
- 柵欄
- 一些常用鎖的性能。
1. 互斥鎖(Mutex)
常用,當一個線程試圖獲取被另一個線程占用的鎖時,它就會被掛起,讓出CPU,直到該鎖被釋放。
- 互斥鎖的實現(xiàn)方式:
-
@synchronized
:實現(xiàn)單例模式 -
NSLock
:不能迭代加鎖,如果發(fā)生兩次lock,而未unlock過,則會產(chǎn)生死鎖問題。
-
1.@synchronized
同步鎖
- 例程:
/**
*設置屬性值
*/
-(void)setMyTestString:(NSString *)myTestString{
@synchronized(self) {
// todo something
_myTestString = myTestString;
}
}
-
常用于單例模式的設計:
例程:
+(instancetype)shareInstance{
// 1.定義一個靜態(tài)實例,初值nil
static TestSynchronized *myClass = nil;
// 2.添加同步鎖,創(chuàng)建實例
@synchronized(self) {
// 3.判斷實例是否創(chuàng)建過,創(chuàng)建過則退出同步鎖,直接返回該實例
if (!myClass) {
// 4.未創(chuàng)建過,則新建一個實例并返回
myClass = [[self alloc] init];
}
}
return myClass;
}
此時為了保證單例模式的更加嚴謹,需要重寫`allocWithZone`方法,保證其他開發(fā)者使用`alloc`和`init`方法時,不再創(chuàng)建新的對象。必要的時候還需要重寫`copyWithZone`方法防止`copy`屬性對單例模式的影響。
iOS中還有一種更加輕便的方法實現(xiàn)單例模式,即使用GCD中的dispatch_once函數(shù)實現(xiàn)。
例程:
+(instancetype)shareInstance{
static TestSynchronized *myClass = nil;
static dispatch_once_t once_token;
dispatch_once(&once_token, ^{
myClass = [[self alloc] init];
});
return myClass;
}
2.NSLock
- 例程:
static NSLock *mylock;
-(void)viewDidLoad {
[super viewDidLoad];
mylock = [[NSLock alloc] init];
}
-(void)myLockTest1{
if ([mylock tryLock]) {
// to do something
[mylock unlock];
}
}
-(void)myLockTest2{
[mylock lock];
// to do something
[mylock unlock];
}
2. 遞歸鎖(Recursive Lock)
遞歸鎖可以被同一線程多次請求,而不會引起死鎖,即在多次被同一個線程進行加鎖時,不會造成死鎖。這主要是用在循環(huán)或遞歸操作中。
可以允許同一線程多次加鎖,而不會造成死鎖。
遞歸鎖會跟蹤它被lock的次數(shù)。每次成功的lock都必須平衡調(diào)用unlock操作。只有所有達到這種平衡,鎖最后才能被釋放,以供其它線程使用。
例程:
NSRecursiveLock *myRecursiveLock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^MyRecursiveLockBlk)(int value);
MyRecursiveLockBlk = ^(int value){
[myRecursiveLock lock];
if (value > 0) {
// to do something
NSLog(@"MyRecursiveLockBlk value = %d", value);
MyRecursiveLockBlk(value - 1);
}
[myRecursiveLock unlock];
};
MyRecursiveLockBlk(6);
});
此時如果將例程中的遞歸鎖換成互斥鎖:
NSRecursiveLock *myRecursiveLock = [[NSRecursiveLock alloc] init];
換成
NSLock *myLock = [[NSLock alloc] init];
,則會發(fā)生死鎖問題。
3. 讀寫鎖(Read-write Lock)
讀寫鎖將訪問者分為讀出和寫入兩種,當讀寫鎖在讀加鎖模式下,所有以讀加鎖方式訪問該資源時,都會獲得訪問權(quán)限,而所有試圖以寫加鎖方式對其加鎖的線程都將阻塞,直到所有的讀鎖釋放。
當在寫加鎖模式下,所有試圖對其加鎖的線程都將阻塞。
例程:
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@property(nonatomic, copy) NSString *rwStr;
@end
@implementation ViewController
pthread_rwlock_t rwlock;
-(void)viewDidLoad {
[super viewDidLoad];
// 初始化讀寫鎖
pthread_rwlock_init(&rwlock,NULL);
__block int i;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
i = 5;
while (i>=0) {
NSString *temp = [NSString stringWithFormat:@"writing == %d", i];
[self writingLock:temp];
i--;
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
i = 5;
while (i>=0) {
[self readingLock];
i--;
}
});
}
// 寫加鎖
-(void)writingLock:(NSString *)temp{
pthread_rwlock_wrlock(&rwlock);
// writing
self.rwStr = temp;
NSLog(@"%@", temp);
pthread_rwlock_unlock(&rwlock);
}
// 讀加鎖
-(NSString *)readingLock{
pthread_rwlock_rdlock(&rwlock);
// reading
NSString *str = self.rwStr;
NSLog(@"reading == %@",self.rwStr);
pthread_rwlock_unlock(&rwlock);
return str;
}
@end
4. 自旋鎖(Spin Lock)
自旋鎖與互斥鎖類似
但不同的是:自旋鎖是非阻塞的,當一個線程無法獲取自旋鎖時,會自旋,直到該鎖被釋放,等待的過程中線程并不會掛起。(實質(zhì)上就是,如果自旋鎖已經(jīng)被別的執(zhí)行單元保持,調(diào)用者就一直循環(huán)在等待該自旋鎖的保持著已經(jīng)釋放了鎖)。
自旋鎖的使用者一般保持鎖的時間很短,此時其效率遠高于互斥鎖。
-
自旋鎖保持期間是搶占失效的
優(yōu)點:效率高,不用進行線程的切換
缺點:如果一個線程霸占鎖的時間過長,自旋會消耗CPU資源
例程:
// 頭文件
#import <libkern/OSAtomic.h>
// 初始化自旋鎖
static OSSpinLock myLock = OS_SPINLOCK_INIT;
// 自旋鎖的使用
-(void)SpinLockTest{
OSSpinLockLock(&myLock);
// to do something
OSSpinLockUnlock(&myLock);
}
5. 分布鎖(Didtributed Lock)
- 跨進程的分布式鎖,是進程間同步的工具,底層是用文件系統(tǒng)實現(xiàn)的互斥鎖,并不強制進程休眠,而是起到告知的作用。
-
NSDistributedLock
沒有實現(xiàn)NSLocking
協(xié)議,所以沒有會阻塞線程的lock
方法,取而代之的是非阻塞的tryLock
方法來獲取鎖,用unlock
方法釋放鎖。 - 如果一個獲取鎖的進程在釋放鎖之前就退出了,那么鎖就一直不能釋放,此時可以通過
breakLock
強行獲取鎖。
6. 條件變量(Condition Variable)
使用情況:如果一個線程需要等待某一條件出現(xiàn)才能繼續(xù)執(zhí)行,而這個條件是由別的線程產(chǎn)生的,這個時候就用到條件變量。常見的情況是:生產(chǎn)者-消費者問題。
-
條件變量可以讓一個線程等待某一條件,當條件滿足時,會收到通知。在獲取條件變量并等待條件發(fā)生的過程中,也會產(chǎn)生多線程的競爭,所以條件變量通常和互斥鎖一起工作。
- NSCondition:是互斥鎖和條件鎖的結(jié)合,即一個線程在等待signal而阻塞時,可以被另一個線程喚醒,由于操作系統(tǒng)實現(xiàn)的差異,即使沒有發(fā)送signal消息,線程也有可能被喚醒,所以需要增加謂詞變量來保證程序的正確性。
- NSConditionLock:與NSCondition的實現(xiàn)機制不一樣,當定義的條件成立的時候會獲取鎖,反之,釋放鎖。
NSCondition的例程:
// 創(chuàng)建鎖
NSCondition *condition = [[NSCondition alloc] init];
static int count = 0;
// 生產(chǎn)者
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while(count<20)
{
[condition lock];
// 生產(chǎn)
count ++;
NSLog(@"生產(chǎn) = %d",count);
[condition signal];
[condition unlock];
}
});
// 消費者
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (count>0)
{
[condition lock];
// 消耗
count --;
NSLog(@"消耗剩余 = %d",count);
[condition unlock];
}
});
NSConditionLock的例程:
// 創(chuàng)建鎖
NSConditionLock *condLock = [[NSConditionLock alloc] initWithCondition:ConditionHASNOT];
static int count = 0;
// 生產(chǎn)者
while(true)
{
[condLock lock];
// 生產(chǎn)
count ++;
[condLock unlockWithCondition:ConditionHAS];
}
// 消費者
while (true)
{
[condLock lockWhenCondition:ConditionHAS];
// 消耗
count --;
[condLock unlockWithCondition:(count<=0 ? ConditionHASNOT : ConditionHAS)];
}
7. 信號量(Semaphore)
- 信號量:可以是一種特殊的互斥鎖,可以是資源的計數(shù)器
- 可以使用GCD中的Dispatch Semaphore實現(xiàn),Dispatch Semaphore是持有計數(shù)的信號,該計數(shù)是多線程編程中的計數(shù)類型信號。計數(shù)為0時等待,計數(shù)大于等于1時,減1為不等待。
8. 柵欄/屏障(Barrier)
- 柵欄必須單獨執(zhí)行,不能與其他任務并發(fā)執(zhí)行,柵欄只對并發(fā)隊列有意義。
- 柵欄只有等待當前隊列所有并發(fā)任務都執(zhí)行完畢后,才會單獨執(zhí)行,帶起執(zhí)行完畢,再按照正常的方式繼續(xù)向下執(zhí)行。
iOS中線程鎖的性能對比:
- No1.自旋鎖
OSSpinLock
耗時最少 - No2.
pthread_mutex
- No3.
NSLock
/NSCondition
/NSRecursiveLock
耗時接近 - No4.
@synchronized
- No5.
NSConditionLock
- 柵欄的性能并沒有很好,在實際開發(fā)中也很少用到(筆者在最近一次面試中就遇到,問柵欄的性能怎么樣?當時并不知道柵欄在實際應用中的性能并不是很理想,又被問到蘋果官方常使用的鎖是什么?應該是自旋鎖,--然而筆者當時還是不知道。。。)
> 劃重點:自旋鎖是線程不安全的在 ibireme 的 不再安全的 OSSpinLock有解釋,進一步的ibireme在文中也有提到蘋果在新系統(tǒng)中已經(jīng)優(yōu)化了 pthread_mutex 的性能,所以它看上去和 OSSpinLock 差距并沒有那么大,所以筆者覺得不妨多了解了解pthread_mutex
- pthread_mutex
__block pthread_mutex_t theLock;
pthread_mutex_init(&theLock, NULL);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&theLock);
NSLog(@"需要線程同步的操作1 開始");
sleep(3);
NSLog(@"需要線程同步的操作1 結(jié)束");
pthread_mutex_unlock(&theLock);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
pthread_mutex_lock(&theLock);
NSLog(@"需要線程同步的操作2");
pthread_mutex_unlock(&theLock);
});
c語言定義下多線程加鎖方式。
- pthread_mutex_init(pthread_mutex_t * mutex,const pthread_mutexattr_t attr);
初始化鎖變量mutex。attr為鎖屬性,NULL值為默認屬性。 - pthread_mutex_lock(pthread_mutex_t* mutex);加鎖
- pthread_mutex_tylock(pthread_mutex_t* mutex);加鎖,但是與2不一樣的是當鎖已經(jīng)在使用的時候,返回為EBUSY,而不是掛起等待。
- pthread_mutex_unlock(pthread_mutex_t* mutex);釋放鎖
- pthread_mutex_destroy(pthread_mutex_t* *mutex);使用完后釋放
代碼執(zhí)行操作結(jié)果如下:
2016-06-30 21:13:32.440 SafeMultiThread[31429:548869] 需要線程同步的操作1 開始
2016-06-30 21:13:35.445 SafeMultiThread[31429:548869] 需要線程同步的操作1 結(jié)束
2016-06-30 21:13:35.446 SafeMultiThread[31429:548866] 需要線程同步的操作2
- pthread_mutex(recursive)
__block pthread_mutex_t theLock;
//pthread_mutex_init(&theLock, NULL);
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveMethod)(int);
RecursiveMethod = ^(int value) {
pthread_mutex_lock(&theLock);
if (value > 0) {
NSLog(@"value = %d", value);
sleep(1);
RecursiveMethod(value - 1);
}
pthread_mutex_unlock(&theLock);
};
RecursiveMethod(5);
});
這是pthread_mutex為了防止在遞歸的情況下出現(xiàn)死鎖而出現(xiàn)的遞歸鎖。作用和NSRecursiveLock遞歸鎖類似。
如果使用pthread_mutex_init(&theLock, NULL);
初始化鎖的話,上面的代碼會出現(xiàn)死鎖現(xiàn)象,但是改成使用遞歸鎖的形式,則沒有問題。