關(guān)于“鎖”
iOS中的鎖,也叫線程鎖。是為了在多線程操作中侠讯,防止同一時(shí)間多個(gè)線程對(duì)共享資源進(jìn)行讀寫(xiě)操作而引入的一種機(jī)制。例如并行隊(duì)列里的異步任務(wù)暑刃,有可能存在多個(gè)線程同時(shí)讀寫(xiě)某一個(gè)對(duì)象的操作厢漩。如線程1正在執(zhí)行寫(xiě)操作,此時(shí)線程2需要執(zhí)行讀操作稍走,如此執(zhí)行線程2獲取到的數(shù)據(jù)肯定是一個(gè)錯(cuò)誤的數(shù)據(jù)袁翁。因此柴底,為了防止這種情況的發(fā)生婿脸,需要引入一種“在同一時(shí)間片段內(nèi)只能有一個(gè)線程在對(duì)共享資源執(zhí)行讀寫(xiě)操作、而時(shí)間片段結(jié)束后能恢復(fù)到多個(gè)線程同時(shí)對(duì)共享資源執(zhí)行讀寫(xiě)操作的機(jī)制”柄驻。而這種機(jī)制狐树,就是我們所說(shuō)的鎖。
“鎖”的作用
在同一時(shí)間段片內(nèi)鸿脓、只允許一個(gè)線程對(duì)某一共享資源執(zhí)行讀寫(xiě)操作抑钟。
需要注意的是:鎖只是改變了鎖內(nèi)共享資源的讀寫(xiě)在時(shí)間片內(nèi)只能有一個(gè)線程去訪問(wèn),但并不會(huì)改變其它線程的執(zhí)行野哭。也就是說(shuō)只有遇到多個(gè)線程同時(shí)訪問(wèn)共享資源的時(shí)候在塔,線程只能一個(gè)一個(gè)的去執(zhí)行。而其它沒(méi)有訪問(wèn)到共享資源的線程拨黔,則依舊是并發(fā)執(zhí)行的蛔溃。
“鎖”的種類
在iOS開(kāi)發(fā)中,NSLock、@synchronized應(yīng)該是我們接觸的最早和做多的一種鎖贺待。至于NSConditionLock徽曲、NSRecursiveLock等一系列的鎖,也會(huì)給大家一一介紹麸塞。
NSLock:普通鎖秃臣,其用法可參見(jiàn)其頭文件。
為2個(gè)方法1個(gè)屬性哪工,以及1個(gè)協(xié)議奥此。2個(gè)方法分別為“嘗試上鎖”、“在某個(gè)時(shí)間之前鎖住”雁比;name則為鎖的名字得院。其所包含的NSLocking協(xié)議,有2個(gè)方法章贞,分別為“上鎖”和“解鎖”祥绞。
其基本用法如下所示:
//自定義并行隊(duì)列
dispatch_queue_tqueue = dispatch_queue_create("com.jiadai.demoOfLock", DISPATCH_QUEUE_CONCURRENT);if(type ==0) {//NSLockNSLock*lock = [[NSLockalloc]init];
__blockinta =0;dispatch_async(queue, ^{NSLog(@"線程1開(kāi)始任務(wù)");
[lock lock];for(inti =0; i<5; i++) {
sleep(1);
a = a +1;NSLog(@"線程1執(zhí)行任務(wù)a = %d",a);if(i==4) {
sleep(1);
}
}
[lock unlock];NSLog(@"線程1解鎖成功");
});dispatch_async(queue, ^{NSLog(@"線程2開(kāi)始任務(wù)");
[lock lock];for(inti =0; i<3; i++) {
sleep(1);
a = a +1;NSLog(@"線程2執(zhí)行任務(wù)a = %d",a);if(i==2) {
sleep(1);
}
}
[lock unlock];NSLog(@"線程2解鎖成功");
});
}
這里自定義了一個(gè)并行異步線程,同事定義了一個(gè)共享資源a鸭限,并且給2個(gè)線程里的任務(wù)都給上鎖蜕径。那么在上鎖之前,2個(gè)任務(wù)理論上應(yīng)該是同時(shí)啟動(dòng)的败京。當(dāng)執(zhí)行鎖的操作時(shí)兜喻,由于只能允許一個(gè)線程執(zhí)行任務(wù),那么這里就有可能任務(wù)1在執(zhí)行也有可能任務(wù)2在執(zhí)行赡麦,總的來(lái)說(shuō)只能有一個(gè)任務(wù)在執(zhí)行朴皆。執(zhí)行結(jié)束后,2個(gè)線程的任務(wù)都執(zhí)行解鎖操作泛粹,線程的任務(wù)又可以回到并行異步了遂铡。我們可參見(jiàn)執(zhí)行的結(jié)果來(lái)進(jìn)一步說(shuō)明鎖是怎么工作的。
可以看得到晶姊,在給線程1和線程2里的任務(wù)都上鎖的情況下扒接,同一時(shí)間片段內(nèi)只能有一個(gè)任務(wù)執(zhí)行,這就保證了操作的互斥性们衙。也就是共享資源钾怔,單次只能執(zhí)行一個(gè)a=a+1的操作。
如果我們?nèi)サ艟€程1或者線程2里的鎖蒙挑,或者去掉全部的鎖宗侦,那么其執(zhí)行窗市,則會(huì)根據(jù)當(dāng)前時(shí)間點(diǎn)拿到的a的值進(jìn)行a=a+1的操作稻据。
例如我們?nèi)サ艟€程2里的鎖
dispatch_async(queue, ^{NSLog(@"線程2開(kāi)始任務(wù)");// [lock lock];for(inti =0; i<3; i++) {
sleep(1);
a = a +1;NSLog(@"線程2執(zhí)行任務(wù)a = %d",a);if(i==2) {
sleep(1);
}
}// [lock unlock];NSLog(@"線程2解鎖成功");
});
其執(zhí)行的結(jié)果:
顯而易見(jiàn),線程2的執(zhí)行并沒(méi)有因?yàn)榫€程1的鎖而受到干擾窗悯。其執(zhí)行時(shí)間與線程1一致,都是間隔1秒執(zhí)行一次梦皮。其執(zhí)行時(shí)的a炭分,為其當(dāng)前時(shí)刻拿到的a(可能先于線程1執(zhí)行,也可能后于線程1執(zhí)行)剑肯。同理線程1的執(zhí)行捧毛,也沒(méi)有受到線程2的影響。
對(duì)于tryLock和lockBeforeDate:(NSDate *)limit让网,2個(gè)方法都是返回BOOL值呀忧。tryLock表示嘗試加鎖,lockBeforeDate則表示在某個(gè)時(shí)間點(diǎn)之前加鎖溃睹。返回YES則表示加鎖成功而账,并在鎖內(nèi)執(zhí)行方法;否則表示加鎖失敗因篇,方法在無(wú)鎖條件下執(zhí)行泞辐。
@synchronized:其用法還是相當(dāng)簡(jiǎn)單的,代碼如下竞滓。
//@synchronized__blockintb1 =0;
__blockintb2 =0;dispatch_async(queue, ^{NSLog(@"線程1開(kāi)始任務(wù)");@synchronized(self) {for(inti =0; i<3; i++) {
sleep(1);
b1 = b1 +1;NSLog(@"線程1執(zhí)行任務(wù)b1 = %d",b1);if(i==2) {
sleep(1);
}
}
}
});dispatch_async(queue, ^{NSLog(@"線程2開(kāi)始任務(wù)");@synchronized(self) {for(inti =0; i<3; i++) {
sleep(1);
b2 = b2 +1;NSLog(@"線程2執(zhí)行任務(wù)b2 = %d",b2);if(i==2) {
sleep(1);
}
}
}
});
在這里咐吼,我定義了2個(gè)異步任務(wù)和2個(gè) 對(duì)象b1、b2商佑,并且執(zhí)行+1操作锯茄。在加鎖之前,2個(gè)任務(wù)應(yīng)該是同時(shí)啟動(dòng)并開(kāi)始執(zhí)行茶没;加鎖之后肌幽,由于2個(gè)子線程的任務(wù)都是加鎖執(zhí)行的,那么在單位時(shí)間內(nèi)只能一個(gè)一個(gè)的執(zhí)行了抓半。結(jié)果如下圖所示:
可看到2個(gè)加鎖的異步任務(wù)喂急,在單位時(shí)間內(nèi)是依次執(zhí)行的。如果去掉其中一個(gè)線程的鎖琅关,則另一個(gè)線程的執(zhí)行并不會(huì)受到加鎖線程的影響煮岁。其執(zhí)行效果,類似于NSLock涣易。
NSConditionLock:條件鎖,其用法可參見(jiàn)其頭文件冶伞。
其用法跟NSLock類似新症,為tryLock和lockBeforeDate。但相對(duì)NSLock多了個(gè)conditon响禽。我們可以通過(guò)具體實(shí)例來(lái)進(jìn)一步了解NSConditionLock的用法徒爹。
//NSConditionLockNSConditionLock*lock = [[NSConditionLockalloc]initWithCondition:0];
__blockintc =0;dispatch_async(queue, ^{NSLog(@"線程1開(kāi)始任務(wù)");
[lock lockWhenCondition:0];for(inti =0; i<3; i++) {
sleep(1);
c = c +1;NSLog(@"線程1執(zhí)行任務(wù)c = %d",c);
}
[lock unlockWithCondition:1];
});dispatch_async(queue, ^{NSLog(@"線程2開(kāi)始任務(wù)");
[lock lockWhenCondition:1];for(inti =0; i<3; i++) {
sleep(1);
c = c +1;NSLog(@"線程2執(zhí)行任務(wù)c = %d",c);
}
[lock unlockWithCondition:2];
});dispatch_async(queue, ^{NSLog(@"線程3開(kāi)始任務(wù)");
[lock lockWhenCondition:2];for(inti =0; i<3; i++) {
sleep(1);
c = c +1;NSLog(@"線程3執(zhí)行任務(wù)c = %d",c);
}
[lock unlockWithCondition:3];
});
首先我們創(chuàng)建了一個(gè)條件鎖荚醒,并且設(shè)定起始條件condition=0。隨后創(chuàng)建了3個(gè)異步任務(wù)隆嗅,并且其任務(wù)是在鎖內(nèi)執(zhí)行的界阁。所以,3個(gè)線程鎖內(nèi)的任務(wù)胖喳,在同一時(shí)間片段內(nèi)只能有一個(gè)在執(zhí)行泡躯。當(dāng)condition=0的時(shí)候,線程1的任務(wù)加鎖丽焊,并且在線程1的任務(wù)執(zhí)行完畢后才執(zhí)行解鎖操作较剃。在解鎖的時(shí)候,給condition重新賦值技健。而此時(shí)線程2滿足加鎖條件写穴,則線程2的任務(wù)會(huì)被立即執(zhí)行。同理雌贱,當(dāng)其它線程滿足加鎖操作時(shí)啊送,則其內(nèi)的任務(wù)也會(huì)被立即執(zhí)行。需要注意的是欣孤,加鎖和解鎖需成對(duì)出現(xiàn)删掀,否則會(huì)造成一些錯(cuò)誤。
其運(yùn)行結(jié)果如下圖所示:
由于NSConditionLock也遵循NSLocking協(xié)議导街,所以可像NSLock那樣直接使用lock和unlock對(duì)任務(wù)執(zhí)行加鎖解鎖操作披泪。
對(duì)于其它幾個(gè)方法,需要根據(jù)返回的BOOL值進(jìn)行判斷是否加鎖成功搬瑰。如果加鎖成功款票,則在方法執(zhí)行完后進(jìn)行解鎖。若沒(méi)有成功泽论,那就不需要解鎖艾少,否則會(huì)造成一些錯(cuò)誤。例如try加鎖成功翼悴,則執(zhí)行鎖的方法缚够,結(jié)束后進(jìn)行解鎖。加鎖失敗鹦赎,則另行處理谍椅。
NSRecursiveLock:遞歸鎖,其用法可參見(jiàn)其頭文件古话。
其用法與NSConditionLock類似雏吭,為tryLock和lockBeforeDate。
//NSRecursiveLockNSRecursiveLock*lock = [[NSRecursiveLockalloc]init];dispatch_async(queue, ^{staticvoid(^MyMethod)(int);
MyMethod = ^(intval){
[lock lock];if(val >0) {
sleep(1);NSLog(@"執(zhí)行任務(wù)val=%d",val);
MyMethod(val-1);
}
[lock unlock];
};
MyMethod(5);
});
例如遞歸方法MyMethod陪踩,內(nèi)有有判斷杖们,當(dāng)val>0的時(shí)候悉抵,則會(huì)重新調(diào)用自身。如果是普通鎖摘完,則會(huì)先執(zhí)行l(wèi)ock姥饰,后又執(zhí)行MyMethod,并嘗試lock孝治。但由于該線程內(nèi)已經(jīng)有一個(gè)lock在執(zhí)行任務(wù)列粪,重新加鎖則會(huì)造成死鎖。此時(shí)選用遞歸鎖荆秦,則允許該線程可被多次lock篱竭,并且在執(zhí)行unlock的時(shí)候,移除所有的lock步绸。
其運(yùn)行結(jié)果如下圖所示:
若將NSConditionLock換成普通的NSLock掺逼,其執(zhí)行結(jié)果如下:
當(dāng)執(zhí)行第二次MyMethod時(shí),發(fā)生死鎖(當(dāng)前線程已存在一個(gè)鎖瓤介,無(wú)法再添加新的鎖)吕喘。
NSCondition:條件鎖,其用法可參見(jiàn)其頭文件刑桑。
其主要提供2個(gè)方法氯质,wait(等待)和signal(發(fā)送信號(hào))。同時(shí)遵循NSLocking協(xié)議祠斧,所以可直接使用lock和unlock方法闻察。
其主要特點(diǎn)為線程任務(wù)等待和發(fā)送信號(hào)喚醒等待中的任務(wù)。具體用法如下所示:
//NSCondition
__blockintd =0;
NSCondition*lock = [[NSConditionalloc]init];
dispatch_async(queue, ^{//上鎖琢锋,并等待[lock lock];
if(d ==0) {
//等待
NSLog(@"線程等待");
[lock wait];
}
//執(zhí)行任務(wù)
2d = d+1;
NSLog(@"執(zhí)行條件任務(wù)2d=%d",d);
//解鎖
[lock unlock];
});dispatch_async(queue, ^{
//上鎖
[lock lock];
d = d +1;
//執(zhí)行任務(wù)1
NSLog(@"執(zhí)行線程任務(wù)1d=%d",d);
//發(fā)送信號(hào)辕漂,通知等待中的線程
NSLog(@"發(fā)送信號(hào)");
[lock signal];
//解鎖
[lock unlock];
});
首先,我們創(chuàng)建了一個(gè)條件鎖吴超,并初始化一個(gè)數(shù)據(jù)d = 0钉嘹。然后我們創(chuàng)建了2個(gè)異步任務(wù),在線程2里鲸阻,進(jìn)行判斷:如果d = 0跋涣,那么線程等待;否則執(zhí)行線程2任務(wù)鸟悴。在線程1里陈辱,我們給任務(wù)上鎖,并執(zhí)行d = d+1的操作遣臼。操作完成后性置,發(fā)送信號(hào)激活處于等待的任務(wù)并對(duì)線程1進(jìn)行解鎖。而此時(shí)揍堰,線程2在收到信號(hào)后鹏浅,在線程1解鎖完成后,執(zhí)行內(nèi)部任務(wù)屏歹,并進(jìn)行線程2解鎖隐砸。從而完成整個(gè)加鎖、等待蝙眶、執(zhí)行任務(wù)季希、發(fā)送信號(hào)、執(zhí)行任務(wù)幽纷、解鎖等過(guò)程式塌。
對(duì)于NSCondition,我們重點(diǎn)需要掌握其線程等待和發(fā)送信號(hào)激活等待線程等方法友浸。
OSSpinLock:自旋鎖
需要導(dǎo)入頭文件:
import<libkern/OSAtomic.h>
具體用法如下圖所示:
//OSSpinLock__block OSSpinLock lock = OS_SPINLOCK_INIT;
__blockinte =0;dispatch_async(queue, ^{
OSSpinLockLock(&lock);for(inti =0; i<3; i++) {
sleep(1);
e = e +1;NSLog(@"線程1執(zhí)行任務(wù)e = %d",e);
}
OSSpinLockUnlock(&lock);
});dispatch_async(queue, ^{
OSSpinLockLock(&lock);for(inti =0; i<3; i++) {
sleep(1);
e = e +1;NSLog(@"線程2執(zhí)行任務(wù)e = %d",e);
}
OSSpinLockUnlock(&lock);
});
其執(zhí)行結(jié)果如下:
`
可見(jiàn)其用法及執(zhí)行結(jié)果峰尝,與NSLock類似。但OSSpinLock已不在安全收恢,在iOS10中首次被遺棄(不建議使用)武学。
pthread_mutex:互斥鎖
需要導(dǎo)入頭文件:
import<pthread.h>
具體用法如下圖所示:
//pthread_mutex 互斥鎖staticpthread_mutex_t lock;
__blockintf =0;
pthread_mutex_init(&lock,NULL);dispatch_async(queue, ^{
pthread_mutex_lock(&lock);for(inti =0; i<3; i++) {
sleep(1);
f = f +1;NSLog(@"線程1執(zhí)行任務(wù)f = %d",f);
}
pthread_mutex_unlock(&lock);
});dispatch_async(queue, ^{
pthread_mutex_lock(&lock);for(inti =0; i<3; i++) {
sleep(1);
f = f +1;NSLog(@"線程2執(zhí)行任務(wù)f = %d",f);
}
pthread_mutex_unlock(&lock);
});
其執(zhí)行結(jié)果如下:
可見(jiàn)其用法及執(zhí)行結(jié)果,與NSLock類似伦意,加鎖解鎖成對(duì)出現(xiàn)火窒。
pthread_mutex(recursive):遞歸鎖。
與pthread_mutex類似驮肉,使用場(chǎng)景與NSRecursiveLock相同熏矿,都是用于單個(gè)線程內(nèi)多次進(jìn)行加鎖操作。具體用法如下所示:
staticpthread_mutex_t lock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);//初始化attr并且給它賦予默認(rèn)pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);//設(shè)置鎖類型离钝,這邊是設(shè)置為遞歸鎖pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);//銷(xiāo)毀一個(gè)屬性對(duì)象票编,在重新進(jìn)行初始化之前該結(jié)構(gòu)不能重新使用dispatch_async(queue, ^{staticvoid(^MyMethod)(int);
MyMethod = ^(intval){
pthread_mutex_lock(&lock);if(val >0) {
sleep(1);NSLog(@"執(zhí)行任務(wù)val=%d",val);
MyMethod(val-1);
}
pthread_mutex_unlock(&lock);
};
MyMethod(5);
});
其執(zhí)行結(jié)果如下:
dispatch_semaphore:信號(hào)量。
其用法與NSCondition有些類似:等待奈辰、發(fā)送信號(hào)栏妖、喚醒等待的任務(wù)。具體使用如下所示:
dispatch_semaphore_t signal = dispatch_semaphore_create(2);
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW,3.0f *NSEC_PER_SEC);dispatch_async(queue, ^{
dispatch_semaphore_wait(signal, overTime);
sleep(1);
NSLog(@"執(zhí)行線程1任務(wù)");
dispatch_semaphore_signal(signal);
});dispatch_async(queue, ^{
dispatch_semaphore_wait(signal, overTime);
sleep(1);
NSLog(@"執(zhí)行線程2任務(wù)");
dispatch_semaphore_signal(signal);
});
dispatch_async(queue, ^{
dispatch_semaphore_wait(signal, overTime);
sleep(1);
NSLog(@"執(zhí)行線程3任務(wù)");
dispatch_semaphore_signal(signal);
});
其執(zhí)行結(jié)果如下:
需要注意的是:在信號(hào)量創(chuàng)建的時(shí)候奖恰,需要帶上一個(gè)并發(fā)執(zhí)行數(shù)吊趾。例如此處我設(shè)置的是1,那單位時(shí)間片內(nèi)只能有一個(gè)線程在執(zhí)行瑟啃。同時(shí)论泛,線程等待需要傳入等待時(shí)間;表示在等待多少時(shí)間后蛹屿,就會(huì)執(zhí)行線程方法屁奏。
性能對(duì)比
鎖的性能,如下圖所示:
總結(jié)
對(duì)于iOS中的鎖错负,需要理解鎖的概念和鎖的作用:是一種解決多線程中共享資源間的互斥訪問(wèn)的一種機(jī)制坟瓢;鎖的使用勇边,會(huì)降低多線程訪問(wèn)同一資源時(shí)執(zhí)行的效率,但是很好的解決了不同線程安全的訪問(wèn)同一資源的問(wèn)題折联;需要注意的是粒褒,同一時(shí)間片內(nèi),只能有一個(gè)加鎖的任務(wù)在執(zhí)行诚镰。而那些沒(méi)有加鎖的并行異步線程任務(wù)奕坟,則仍是多個(gè)線程同時(shí)執(zhí)行的;對(duì)于鎖清笨,還需要記住常用的幾種鎖以及各種鎖之間的差異與使用場(chǎng)景月杉,這對(duì)于我們?cè)诙嗑€程開(kāi)發(fā)中對(duì)于資源的安全性將會(huì)更有幫助。