題記:雖然有些事情的發(fā)生可能是你預(yù)料之中的耸序,但是當(dāng)它真正的發(fā)生了的時(shí)候忍些,還是很難以接受的,還是需要一點(diǎn)時(shí)間坎怪,去緩和這種消極的情緒罢坝,盡快站起來(lái)吧!加油搅窿!花了一天半的時(shí)間嘁酿,各種查閱資料,總結(jié)了iOS中關(guān)于線程鎖的知識(shí)男应,希望我能從中學(xué)到一些闹司,也希望可以幫到同樣有需要的你!(文中如有錯(cuò)誤沐飘,還請(qǐng)?zhí)岢鲇巫黄鸾涣鳎?/em>
本文主要介紹:
- 互斥鎖
- 遞歸鎖
- 讀寫(xiě)鎖
- 自旋鎖
- 分布鎖
- 條件變量
- 信號(hào)量
- 柵欄
- 一些常用鎖的性能。
1. 互斥鎖(Mutex)
常用薪铜,當(dāng)一個(gè)線程試圖獲取被另一個(gè)線程占用的鎖時(shí)众弓,它就會(huì)被掛起,讓出CPU隔箍,直到該鎖被釋放。
- 互斥鎖的實(shí)現(xiàn)方式:
-
@synchronized
:實(shí)現(xiàn)單例模式 -
NSLock
:不能迭代加鎖脚乡,如果發(fā)生兩次lock蜒滩,而未unlock過(guò)滨达,則會(huì)產(chǎn)生死鎖問(wèn)題。
-
1.@synchronized
同步鎖
- 例程:
/**
*設(shè)置屬性值
*/
-(void)setMyTestString:(NSString *)myTestString{
@synchronized(self) {
// todo something
_myTestString = myTestString;
}
}
-
常用于單例模式的設(shè)計(jì):
例程:
+(instancetype)shareInstance{
// 1.定義一個(gè)靜態(tài)實(shí)例俯艰,初值nil
static TestSynchronized *myClass = nil;
// 2.添加同步鎖捡遍,創(chuàng)建實(shí)例
@synchronized(self) {
// 3.判斷實(shí)例是否創(chuàng)建過(guò),創(chuàng)建過(guò)則退出同步鎖竹握,直接返回該實(shí)例
if (!myClass) {
// 4.未創(chuàng)建過(guò)画株,則新建一個(gè)實(shí)例并返回
myClass = [[self alloc] init];
}
}
return myClass;
}
此時(shí)為了保證單例模式的更加嚴(yán)謹(jǐn),需要重寫(xiě)`allocWithZone`方法啦辐,保證其他開(kāi)發(fā)者使用`alloc`和`init`方法時(shí)谓传,不再創(chuàng)建新的對(duì)象。必要的時(shí)候還需要重寫(xiě)`copyWithZone`方法防止`copy`屬性對(duì)單例模式的影響芹关。
iOS中還有一種更加輕便的方法實(shí)現(xiàn)單例模式续挟,即使用GCD中的dispatch_once函數(shù)實(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)
遞歸鎖可以被同一線程多次請(qǐng)求侥衬,而不會(huì)引起死鎖诗祸,即在多次被同一個(gè)線程進(jìn)行加鎖時(shí),不會(huì)造成死鎖轴总。這主要是用在循環(huán)或遞歸操作中直颅。
可以允許同一線程多次加鎖,而不會(huì)造成死鎖怀樟。
遞歸鎖會(huì)跟蹤它被lock的次數(shù)际乘。每次成功的lock都必須平衡調(diào)用unlock操作。只有所有達(dá)到這種平衡漂佩,鎖最后才能被釋放脖含,以供其它線程使用。
例程:
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);
});
此時(shí)如果將例程中的遞歸鎖換成互斥鎖:
NSRecursiveLock *myRecursiveLock = [[NSRecursiveLock alloc] init];
換成
NSLock *myLock = [[NSLock alloc] init];
投蝉,則會(huì)發(fā)生死鎖問(wèn)題养葵。
3. 讀寫(xiě)鎖(Read-write Lock)
讀寫(xiě)鎖將訪問(wèn)者分為讀出和寫(xiě)入兩種,當(dāng)讀寫(xiě)鎖在讀加鎖模式下瘩缆,所有以讀加鎖方式訪問(wèn)該資源時(shí)关拒,都會(huì)獲得訪問(wèn)權(quán)限,而所有試圖以寫(xiě)加鎖方式對(duì)其加鎖的線程都將阻塞庸娱,直到所有的讀鎖釋放着绊。
當(dāng)在寫(xiě)加鎖模式下,所有試圖對(duì)其加鎖的線程都將阻塞熟尉。
例程:
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@property(nonatomic, copy) NSString *rwStr;
@end
@implementation ViewController
pthread_rwlock_t rwlock;
-(void)viewDidLoad {
[super viewDidLoad];
// 初始化讀寫(xiě)鎖
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--;
}
});
}
// 寫(xiě)加鎖
-(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)
自旋鎖與互斥鎖類(lèi)似
但不同的是:自旋鎖是非阻塞的归露,當(dāng)一個(gè)線程無(wú)法獲取自旋鎖時(shí),會(huì)自旋斤儿,直到該鎖被釋放剧包,等待的過(guò)程中線程并不會(huì)掛起恐锦。(實(shí)質(zhì)上就是,如果自旋鎖已經(jīng)被別的執(zhí)行單元保持疆液,調(diào)用者就一直循環(huán)在等待該自旋鎖的保持著已經(jīng)釋放了鎖)一铅。
自旋鎖的使用者一般保持鎖的時(shí)間很短,此時(shí)其效率遠(yuǎn)高于互斥鎖堕油。
-
自旋鎖保持期間是搶占失效的
優(yōu)點(diǎn):效率高潘飘,不用進(jìn)行線程的切換
缺點(diǎn):如果一個(gè)線程霸占鎖的時(shí)間過(guò)長(zhǎng),自旋會(huì)消耗CPU資源
例程:
// 頭文件
#import <libkern/OSAtomic.h>
// 初始化自旋鎖
static OSSpinLock myLock = OS_SPINLOCK_INIT;
// 自旋鎖的使用
-(void)SpinLockTest{
OSSpinLockLock(&myLock);
// to do something
OSSpinLockUnlock(&myLock);
}
5. 分布鎖(Didtributed Lock)
- 跨進(jìn)程的分布式鎖掉缺,是進(jìn)程間同步的工具卜录,底層是用文件系統(tǒng)實(shí)現(xiàn)的互斥鎖,并不強(qiáng)制進(jìn)程休眠攀圈,而是起到告知的作用暴凑。
-
NSDistributedLock
沒(méi)有實(shí)現(xiàn)NSLocking
協(xié)議,所以沒(méi)有會(huì)阻塞線程的lock
方法赘来,取而代之的是非阻塞的tryLock
方法來(lái)獲取鎖现喳,用unlock
方法釋放鎖。 - 如果一個(gè)獲取鎖的進(jìn)程在釋放鎖之前就退出了犬辰,那么鎖就一直不能釋放嗦篱,此時(shí)可以通過(guò)
breakLock
強(qiáng)行獲取鎖。
6. 條件變量(Condition Variable)
使用情況:如果一個(gè)線程需要等待某一條件出現(xiàn)才能繼續(xù)執(zhí)行幌缝,而這個(gè)條件是由別的線程產(chǎn)生的灸促,這個(gè)時(shí)候就用到條件變量。常見(jiàn)的情況是:生產(chǎn)者-消費(fèi)者問(wèn)題涵卵。
-
條件變量可以讓一個(gè)線程等待某一條件浴栽,當(dāng)條件滿足時(shí),會(huì)收到通知轿偎。在獲取條件變量并等待條件發(fā)生的過(guò)程中典鸡,也會(huì)產(chǎn)生多線程的競(jìng)爭(zhēng),所以條件變量通常和互斥鎖一起工作坏晦。
- NSCondition:是互斥鎖和條件鎖的結(jié)合萝玷,即一個(gè)線程在等待signal而阻塞時(shí),可以被另一個(gè)線程喚醒昆婿,由于操作系統(tǒng)實(shí)現(xiàn)的差異球碉,即使沒(méi)有發(fā)送signal消息,線程也有可能被喚醒仓蛆,所以需要增加謂詞變量來(lái)保證程序的正確性睁冬。
- NSConditionLock:與NSCondition的實(shí)現(xiàn)機(jī)制不一樣,當(dāng)定義的條件成立的時(shí)候會(huì)獲取鎖多律,反之痴突,釋放鎖搂蜓。
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];
}
});
// 消費(fèi)者
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];
}
// 消費(fèi)者
while (true)
{
[condLock lockWhenCondition:ConditionHAS];
// 消耗
count --;
[condLock unlockWithCondition:(count<=0 ? ConditionHASNOT : ConditionHAS)];
}
7. 信號(hào)量(Semaphore)
- 信號(hào)量:可以是一種特殊的互斥鎖狼荞,可以是資源的計(jì)數(shù)器
- 可以使用GCD中的Dispatch Semaphore實(shí)現(xiàn)辽装,Dispatch Semaphore是持有計(jì)數(shù)的信號(hào),該計(jì)數(shù)是多線程編程中的計(jì)數(shù)類(lèi)型信號(hào)相味。計(jì)數(shù)為0時(shí)等待拾积,計(jì)數(shù)大于等于1時(shí),減1為不等待丰涉。
8. 柵欄/屏障(Barrier)
- 柵欄必須單獨(dú)執(zhí)行拓巧,不能與其他任務(wù)并發(fā)執(zhí)行,柵欄只對(duì)并發(fā)隊(duì)列有意義一死。
- 柵欄只有等待當(dāng)前隊(duì)列所有并發(fā)任務(wù)都執(zhí)行完畢后肛度,才會(huì)單獨(dú)執(zhí)行,帶起執(zhí)行完畢投慈,再按照正常的方式繼續(xù)向下執(zhí)行承耿。
iOS中線程鎖的性能對(duì)比:
- No1.自旋鎖
OSSpinLock
耗時(shí)最少 - No2.
pthread_mutex
- No3.
NSLock
/NSCondition
/NSRecursiveLock
耗時(shí)接近 - No4.
@synchronized
- No5.
NSConditionLock
- 柵欄的性能并沒(méi)有很好伪煤,在實(shí)際開(kāi)發(fā)中也很少用到(筆者在最近一次面試中就遇到加袋,問(wèn)柵欄的性能怎么樣?當(dāng)時(shí)并不知道柵欄在實(shí)際應(yīng)用中的性能并不是很理想抱既,又被問(wèn)到蘋(píng)果官方常使用的鎖是什么职烧?應(yīng)該是自旋鎖,--然而筆者當(dāng)時(shí)還是不知道防泵。蚀之。。)
> 劃重點(diǎn):自旋鎖是線程不安全的在 ibireme 的 不再安全的 OSSpinLock有解釋?zhuān)M(jìn)一步的ibireme在文中也有提到蘋(píng)果在新系統(tǒng)中已經(jīng)優(yōu)化了 pthread_mutex 的性能捷泞,所以它看上去和 OSSpinLock 差距并沒(méi)有那么大足删,所以筆者覺(jué)得不妨多了解了解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 開(kāi)始");
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語(yǔ)言定義下多線程加鎖方式。
- pthread_mutex_init(pthread_mutex_t * mutex,const pthread_mutexattr_t attr);
初始化鎖變量mutex肚邢。attr為鎖屬性壹堰,NULL值為默認(rèn)屬性。 - pthread_mutex_lock(pthread_mutex_t* mutex);加鎖
- pthread_mutex_tylock(pthread_mutex_t* mutex);加鎖骡湖,但是與2不一樣的是當(dāng)鎖已經(jīng)在使用的時(shí)候贱纠,返回為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 開(kāi)始
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遞歸鎖類(lèi)似。
如果使用pthread_mutex_init(&theLock, NULL);
初始化鎖的話浦夷,上面的代碼會(huì)出現(xiàn)死鎖現(xiàn)象辖试,但是改成使用遞歸鎖的形式辜王,則沒(méi)有問(wèn)題。