一、線程鎖相關(guān)概念
線程鎖:我們?cè)谑褂枚嗑€程的時(shí)候多個(gè)線程可能會(huì)訪問同一塊資源,這樣就很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全等問題,這時(shí)候就需要保證同一個(gè)時(shí)間只允許有限個(gè)線程訪問這一塊資源,所有就需要線程鎖,鎖也分為自旋鎖,互斥鎖,信號(hào)量等等。比如一個(gè)線程 A 進(jìn)入需要保護(hù)代碼之前添加簡(jiǎn)單的互斥鎖,另一個(gè)線程 B 就無法訪問,只有等待前一個(gè)線程 A 執(zhí)行完被保護(hù)的代碼后解鎖,B 線程才能訪問被保護(hù)代碼。
臨界區(qū):指的是一塊對(duì)公共資源進(jìn)行訪問的代碼,并非一種機(jī)制或是算法。
時(shí)間片(timeslice):分時(shí)操作系統(tǒng)分配給每個(gè)正在運(yùn)行的進(jìn)程微觀上的一段CPU時(shí)間(在搶占內(nèi)核中是:從進(jìn)程開始運(yùn)行直到被搶占的時(shí)間)。現(xiàn)代操作系統(tǒng)(如:Windows、Linux、Mac OS X等)允許同時(shí)運(yùn)行多個(gè)進(jìn)程 —— 例如,你可以在打開音樂播放器聽音樂的同時(shí)用瀏覽器瀏覽網(wǎng)頁并下載文件。事實(shí)上,由于一臺(tái)計(jì)算機(jī)通常只有一個(gè)CPU,所以永遠(yuǎn)不可能真正地同時(shí)運(yùn)行多個(gè)任務(wù)。這些進(jìn)程“看起來像”同時(shí)運(yùn)行的,實(shí)則是輪番穿插地運(yùn)行,由于時(shí)間片通常很短(在Linux上為5ms-800ms),用戶不會(huì)感覺到。
自旋鎖:是用于多線程同步的一種鎖,線程反復(fù)檢查鎖變量是否可用。由于線程在這一過程中保持執(zhí)行,因此是一種忙等(busy-wait)狀態(tài)。一旦獲取了自旋鎖,線程會(huì)一直保持該鎖,直至顯式釋放自旋鎖。 自旋鎖避免了進(jìn)程上下文的調(diào)度開銷,因此對(duì)于線程只會(huì)阻塞很短時(shí)間的場(chǎng)合是有效的,如果臨界區(qū)的執(zhí)行時(shí)間過長(zhǎng),使用自旋鎖不是個(gè)好主意。
互斥鎖(Mutex):是一種用于多線程編程中,防止兩條線程同時(shí)對(duì)同一公共資源(比如全局變量)進(jìn)行讀寫的機(jī)制。該目的通過將代碼切片成一個(gè)一個(gè)的臨界區(qū)而達(dá)成。
讀寫鎖:是計(jì)算機(jī)程序的并發(fā)控制的一種同步機(jī)制,也稱“共享-互斥鎖”、多讀者-單寫者鎖) 用于解決多線程對(duì)公共資源讀寫問題。讀操作可并發(fā)重入,寫操作是互斥的。 讀寫鎖通常用互斥鎖、條件變量、信號(hào)量實(shí)現(xiàn)。
信號(hào)量(semaphore):是一種更高級(jí)的同步機(jī)制,互斥鎖可以說是semaphore在僅取值0/1時(shí)的特例。信號(hào)量可以有更多的取值空間,用來實(shí)現(xiàn)更加復(fù)雜的同步,而不單單是線程間互斥。信號(hào)量的值為零時(shí),會(huì)使線程進(jìn)入睡眠狀態(tài),主動(dòng)讓出時(shí)間片,主動(dòng)讓出時(shí)間片并不總是代表效率高。讓出時(shí)間片會(huì)導(dǎo)致操作系統(tǒng)切換到另一個(gè)線程,這種上下文切換通常需要 10 微秒左右,而且至少需要兩次切換。如果等待時(shí)間很短,比如只有幾個(gè)微秒,忙等就比線程睡眠更高效。
條件鎖:就是條件變量,當(dāng)進(jìn)程的某些資源要求不滿足時(shí)就進(jìn)入休眠,也就是鎖住了。當(dāng)資源被分配到了,條件鎖打開,進(jìn)程繼續(xù)運(yùn)行。
二、iOS中的十一種鎖
1.OSSpinLock(自旋鎖)
2.os_unfair_lock(iOS 10以后,蘋果提供用來代替OSSpinLock)
3.dispatch_semaphore (信號(hào)量)
4.pthread_mutex(互斥鎖)
5.pthread_mutex(recursive) (遞歸鎖)
6.NSLock(互斥鎖)
7.NSCondition(條件鎖)
8.NSRecursiveLock(遞歸鎖)
9.NSConditionLock(條件鎖)
10.@synchronized(條件鎖)
11.pthread_rwlock(讀寫鎖)
1.OSSpinLock(自旋鎖)
測(cè)試中效率最高的鎖, 不過經(jīng)YYKit作者確認(rèn), OSSpinLock已經(jīng)不再線程安全,OSSpinLock有潛在的優(yōu)先級(jí)反轉(zhuǎn)問題.不再安全的 OSSpinLock。在 iOS 10/macOS 10.12 發(fā)布時(shí),蘋果提供了新的 os_unfair_lock 作為 OSSpinLock 的替代,并且將 OSSpinLock 標(biāo)記為了 Deprecated。
OS_SPINLOCK_INIT: 默認(rèn)值為 0,在 locked 狀態(tài)時(shí)就會(huì)大于 0,unlocked狀態(tài)下為 0
OSSpinLockLock(&oslock):上鎖,參數(shù)為 OSSpinLock 地址
OSSpinLockUnlock(&oslock):解鎖,參數(shù)為 OSSpinLock 地址
OSSpinLockTry(&oslock):嘗試加鎖,可以加鎖則立即加鎖并返回 YES,反之返回 NO
OSSpinLockLock測(cè)試代碼
#import <libkern/OSAtomic.h>
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//設(shè)置票數(shù)
self.totalCount = 5;
spinLock = OS_SPINLOCK_INIT;
dispatch_queue_t queue = dispatch_queue_create("saleTicket", DISPATCH_QUEUE_CONCURRENT);
//售票線程1
dispatch_async(queue, ^{
[NSThread currentThread].name = @"售票線程A";
[self saleTicket];
});
NSLog(@"-----------------------");
//售票線程2
dispatch_async(queue, ^{
[NSThread currentThread].name = @"售票線程B";
[self saleTicket];
});
}
-(void)saleTicket{
while (1) {
// [NSThread sleepForTimeInterval:1];
NSLog(@"%@準(zhǔn)備上鎖",[NSThread currentThread].name);
OSSpinLockLock(&spinLock);
NSLog(@"%@已上鎖",[NSThread currentThread].name);
if (self.totalCount > 0) {
self.totalCount --;
NSLog(@"%@賣出去了一張票,還剩下%zd張票", [NSThread currentThread].name,self.totalCount);
OSSpinLockUnlock(&spinLock);
NSLog(@"%@解鎖",[NSThread currentThread].name);
}else{
NSLog(@"%@:票已售完",[NSThread currentThread].name);
OSSpinLockUnlock(&spinLock);
NSLog(@"%@解鎖",[NSThread currentThread].name);
break;
}
}
}
輸出結(jié)果(每次執(zhí)行結(jié)果不相同)
11:32:06.352257+0800 threadTest[2739:696809] 售票線程A準(zhǔn)備上鎖
11:32:06.352282+0800 threadTest[2739:696809] 售票線程A已上鎖
11:32:06.352614+0800 threadTest[2739:696809] 售票線程A賣出去了一張票,還剩下2張票
11:32:06.353249+0800 threadTest[2739:696809] 售票線程A解鎖
11:32:06.353371+0800 threadTest[2739:696809] 售票線程A準(zhǔn)備上鎖
11:32:06.352793+0800 threadTest[2739:696981] 售票線程B準(zhǔn)備上鎖
11:32:06.353399+0800 threadTest[2739:696809] 售票線程A已上鎖
11:32:06.353429+0800 threadTest[2739:696809] 售票線程A賣出去了一張票,還剩下1張票
11:32:06.353464+0800 threadTest[2739:696809] 售票線程A解鎖
11:32:06.353496+0800 threadTest[2739:696809] 售票線程A準(zhǔn)備上鎖
11:32:06.353656+0800 threadTest[2739:696981] 售票線程B已上鎖
11:32:06.353685+0800 threadTest[2739:696981] 售票線程B賣出去了一張票,還剩下0張票
11:32:06.353711+0800 threadTest[2739:696981] 售票線程B解鎖
11:32:06.353741+0800 threadTest[2739:696981] 售票線程B準(zhǔn)備上鎖
11:32:06.353772+0800 threadTest[2739:696981] 售票線程B已上鎖
11:32:06.356014+0800 threadTest[2739:696981] 售票線程B:票已售完
11:32:06.356058+0800 threadTest[2739:696981] 售票線程B解鎖
11:32:06.356621+0800 threadTest[2739:696809] 售票線程A已上鎖
11:32:06.357062+0800 threadTest[2739:696809] 售票線程A:票已售完
11:32:06.357103+0800 threadTest[2739:696809] 售票線程A解鎖
OSSpinLockTry 嘗試加鎖,可以加鎖則立即加鎖并返回 YES,反之返回 NO
-(void)saleTicket2{
while (1) {
// [NSThread sleepForTimeInterval:1];
NSLog(@"%@準(zhǔn)備上鎖",[NSThread currentThread].name);
// OSSpinLockLock(&spinLock);
//嘗試加鎖,可以加鎖則立即加鎖并返回 YES,反之返回 NO
//當(dāng)前線程鎖失敗,也可以繼續(xù)其它任務(wù),用 OSSpinLockTry 合適
//當(dāng)前線程只有鎖成功后,才會(huì)做一些有意義的工作,那就OSSpinLockLock,沒必要輪詢OSSpinLockTry
if (OSSpinLockTry(&spinLock)) {
NSLog(@"%@已上鎖",[NSThread currentThread].name);
if (self.totalCount > 0) {
self.totalCount --;
NSLog(@"%@賣出去了一張票,還剩下%zd張票", [NSThread currentThread].name,self.totalCount);
OSSpinLockUnlock(&spinLock);
NSLog(@"%@解鎖",[NSThread currentThread].name);
}else{
NSLog(@"%@:票已售完",[NSThread currentThread].name);
OSSpinLockUnlock(&spinLock);
NSLog(@"%@解鎖",[NSThread currentThread].name);
break;
}
}else{
NSLog(@"%@等待中",[NSThread currentThread].name);
}
}
}
輸出結(jié)果
13:53:40.635663+0800 threadTest[296:19536] 售票線程A準(zhǔn)備上鎖
13:53:40.635689+0800 threadTest[296:19536] 售票線程A已上鎖
13:53:40.635717+0800 threadTest[296:19536] 售票線程A賣出去了一張票,還剩下2張票
13:53:40.635929+0800 threadTest[296:19536] 售票線程A解鎖
13:53:40.635967+0800 threadTest[296:19536] 售票線程A準(zhǔn)備上鎖
13:53:40.635773+0800 threadTest[296:19780] 售票線程B準(zhǔn)備上鎖
13:53:40.635993+0800 threadTest[296:19536] 售票線程A已上鎖
13:53:40.636022+0800 threadTest[296:19536] 售票線程A賣出去了一張票,還剩下1張票
13:53:40.636025+0800 threadTest[296:19780] 售票線程B等待中
13:53:40.636049+0800 threadTest[296:19536] 售票線程A解鎖
13:53:40.636064+0800 threadTest[296:19780] 售票線程B準(zhǔn)備上鎖
13:53:40.636075+0800 threadTest[296:19536] 售票線程A準(zhǔn)備上鎖
13:53:40.636093+0800 threadTest[296:19780] 售票線程B已上鎖
13:53:40.636102+0800 threadTest[296:19536] 售票線程A等待中
13:53:40.636126+0800 threadTest[296:19780] 售票線程B賣出去了一張票,還剩下0張票
13:53:40.636160+0800 threadTest[296:19780] 售票線程B解鎖
13:53:40.636520+0800 threadTest[296:19780] 售票線程B準(zhǔn)備上鎖
13:53:40.636548+0800 threadTest[296:19780] 售票線程B已上鎖
13:53:40.636575+0800 threadTest[296:19780] 售票線程B:票已售完
13:53:40.636600+0800 threadTest[296:19780] 售票線程B解鎖
13:53:40.636129+0800 threadTest[296:19536] 售票線程A準(zhǔn)備上鎖
13:53:40.636652+0800 threadTest[296:19536] 售票線程A已上鎖
13:53:40.636678+0800 threadTest[296:19536] 售票線程A:票已售完
13:53:40.636704+0800 threadTest[296:19536] 售票線程A解鎖
2.os_unfair_lock(iOS 10以后,蘋果提供用來代替OSSpinLock)
測(cè)試代碼和OSSpinLock相同 替換對(duì)應(yīng)上鎖解鎖代碼
#import <os/lock.h>
// 初始化(os_unfair_lock_t)
os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT);
// 加鎖
os_unfair_lock_lock(unfairLock);
// 解鎖
os_unfair_lock_unlock(unfairLock);
// 嘗試加鎖,可以加鎖則立即加鎖并返回 YES,反之返回 NO
os_unfair_lock_trylock(unfairLock)
//或者
// 初始化(os_unfair_lock)
os_unfair_lock unfair_lock = OS_UNFAIR_LOCK_INIT;
// 加鎖
os_unfair_lock_lock(&unfair_lock);
// 解鎖
os_unfair_lock_unlock(&unfair_lock);
// 嘗試加鎖,可以加鎖則立即加鎖并返回 YES,反之返回 NO
os_unfair_lock_trylock(&unfair_lock);
3.dispatch_semaphore (信號(hào)量)
dispatch_semaphore_create(1):創(chuàng)建信號(hào)量,傳入值必須 >=0, 若傳入為 0 則阻塞線程并等待timeout,時(shí)間到后會(huì)執(zhí)行其后的語句
dispatch_semaphore_wait(signal, overTime):等待信號(hào)量,可以理解為 lock,會(huì)使得 signal 值 -1
dispatch_semaphore_signal(signal):發(fā)送信號(hào)量,可以理解為 unlock,會(huì)使得 signal 值 +1
關(guān)于信號(hào)量,在其他文章中看到的一個(gè)比較形象的比喻:
停車場(chǎng)剩余4個(gè)車位,那么即使同時(shí)來了四輛車也能停的下。如果此時(shí)來了五輛車,那么就有一輛需要等待。
信號(hào)量的值(signal)就相當(dāng)于剩余車位的數(shù)目,dispatch_semaphore_wait 函數(shù)就相當(dāng)于來了一輛車,dispatch_semaphore_signal 就相當(dāng)于走了一輛車。停車位的剩余數(shù)目在初始化的時(shí)候就已經(jīng)指明了(dispatch_semaphore_create(long value)),調(diào)用一次 dispatch_semaphore_signal,剩余的車位就增加一個(gè);調(diào)用一次dispatch_semaphore_wait 剩余車位就減少一個(gè);當(dāng)剩余車位為 0 時(shí),再來車(即調(diào)用 dispatch_semaphore_wait)就只能等待。有可能同時(shí)有幾輛車等待一個(gè)停車位。有些車主沒有耐心,給自己設(shè)定了一段等待時(shí)間,這段時(shí)間內(nèi)等不到停車位就走了,如果等到了就開進(jìn)去停車。而有些車主就像把車停在這,所以就一直等下去。
dispatch_semaphore測(cè)試代碼01 - 簡(jiǎn)單模擬停車
dispatch_queue_t queue = dispatch_queue_create("dispatch_semaphore_test2", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);// 停車場(chǎng)車位數(shù)
for (int i = 0; i < 4; i ++) {
[NSThread currentThread].name = [NSString stringWithFormat:@"汽車%d",i+1];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%@進(jìn)入",[NSThread currentThread]);
int x = arc4random() % 4;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((x+1) * NSEC_PER_SEC)), queue, ^{
[NSThread currentThread].name = [NSString stringWithFormat:@"汽車%d",i+1];
NSLog(@"%@停車%d秒后離開",[NSThread currentThread],(x+1));
dispatch_semaphore_signal(semaphore);
});
}
NSLog(@"----------------------------");
輸出結(jié)果
13:17:19.709194+0800 threadTest[930:177270] <NSThread: 0x280d0d8c0>{number = 1, name = 汽車1}進(jìn)入
13:17:19.710074+0800 threadTest[930:177270] <NSThread: 0x280d0d8c0>{number = 1, name = 汽車2}進(jìn)入
13:17:21.888932+0800 threadTest[930:177395] <NSThread: 0x280d608c0>{number = 3, name = 汽車1}停車2秒后離開
13:17:21.889121+0800 threadTest[930:177270] <NSThread: 0x280d0d8c0>{number = 1, name = 汽車3}進(jìn)入
13:17:24.098562+0800 threadTest[930:177515] <NSThread: 0x280d5b100>{number = 4, name = 汽車2}停車4秒后離開
13:17:24.099205+0800 threadTest[930:177270] <NSThread: 0x280d0d8c0>{number = 1, name = 汽車4}進(jìn)入
13:17:24.099399+0800 threadTest[930:177270] ----------------------------
13:17:26.289179+0800 threadTest[930:177395] <NSThread: 0x280d608c0>{number = 3, name = 汽車3}停車4秒后離開
13:17:26.289179+0800 threadTest[930:177515] <NSThread: 0x280d5b100>{number = 4, name = 汽車4}停車2秒后離開
dispatch_semaphore測(cè)試代碼02
多個(gè)異步請(qǐng)求完成之后再執(zhí)行最后的任務(wù)(用dispatch_group_enter(dispatch_group_t group) 和 dispatch_group_leave(dispatch_group_t group)也可以實(shí)現(xiàn))
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("dispatch_semaphore_test1", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_group_async(group, queue, ^{
NSLog(@"任務(wù)01開始%@",[NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)01完成%@",[NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
NSLog(@"任務(wù)01等待完成%@",[NSThread currentThread]);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
dispatch_group_async(group, queue, ^{
NSLog(@"任務(wù)02開始%@",[NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)02完成%@",[NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
NSLog(@"任務(wù)02等待完成%@",[NSThread currentThread]);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有任務(wù)已完成");
});
輸出結(jié)果
18:13:02.689950+0800 threadTest[540:66884] 任務(wù)01開始<NSThread: 0x28075cec0>{number = 4, name = (null)}
18:13:02.690544+0800 threadTest[540:66884] 任務(wù)01等待完成<NSThread: 0x28075cec0>{number = 4, name = (null)}
18:13:02.690597+0800 threadTest[540:66944] 任務(wù)02開始<NSThread: 0x28075cb40>{number = 3, name = (null)}
18:13:02.690754+0800 threadTest[540:66944] 任務(wù)02等待完成<NSThread: 0x28075cb40>{number = 3, name = (null)}
18:13:03.737854+0800 threadTest[540:66847] 任務(wù)02完成<NSThread: 0x280735a40>{number = 1, name = main}
18:13:04.837859+0800 threadTest[540:66847] 任務(wù)01完成<NSThread: 0x280735a40>{number = 1, name = main}
18:13:04.838539+0800 threadTest[540:66847] 所有任務(wù)已完成
18:13:11.508223+0800 threadTest[540:66944] 任務(wù)01開始<NSThread: 0x28075cb40>{number = 3, name = (null)}
18:13:11.508920+0800 threadTest[540:66964] 任務(wù)02開始<NSThread: 0x28075c800>{number = 5, name = (null)}
18:13:11.509366+0800 threadTest[540:66944] 任務(wù)01等待完成<NSThread: 0x28075cb40>{number = 3, name = (null)}
18:13:11.509425+0800 threadTest[540:66964] 任務(wù)02等待完成<NSThread: 0x28075c800>{number = 5, name = (null)}
18:13:12.537780+0800 threadTest[540:66847] 任務(wù)02完成<NSThread: 0x280735a40>{number = 1, name = main}
18:13:13.654482+0800 threadTest[540:66847] 任務(wù)01完成<NSThread: 0x280735a40>{number = 1, name = main}
18:13:13.655111+0800 threadTest[540:66847] 所有任務(wù)已完成
4.pthread_mutex(互斥鎖)
測(cè)試代碼和OSSpinLock相同 替換對(duì)應(yīng)上鎖解鎖代碼
#import <pthread/pthread.h>
// 普通初始化
pthread_mutex_t mutexLock;
pthread_mutex_init(&mutexLock, NULL);
// 宏初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 加鎖
pthread_mutex_lock(&mutexLock);
// 解鎖
pthread_mutex_unlock(&mutexLock);
// 嘗試加鎖,可以加鎖時(shí)返回的是 0,否則返回一個(gè)錯(cuò)誤(OSSpinLockTry(&spinLock)返回YES或者NO)
pthread_mutex_trylock(&mutexLock)
5.pthread_mutex(recursive) (遞歸鎖)
注: 遞歸鎖可以被同一線程多次請(qǐng)求,而不會(huì)引起死鎖。即在同一線程中在未解鎖之前還可以上鎖, 執(zhí)行鎖中的代碼。這主要是用在循環(huán)或遞歸操作中。
pthread_mutex_t mutex_t;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化attr
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE); //設(shè)置鎖類型,這邊是設(shè)置為遞歸鎖
pthread_mutex_init(&mutex_t, &attr);
pthread_mutexattr_destroy(&attr); //銷毀一個(gè)屬性對(duì)象,在重新進(jìn)行初始化之前該結(jié)構(gòu)不能重新使用
pthread_mutex_lock(&mutex_t);// 加鎖
pthread_mutex_unlock(&mutex_t);// 解鎖
pthread_mutexattr_t設(shè)置的相關(guān)函數(shù)及其說明
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self recursive:2];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self recursive:2];
});
-(void)recursive:(int)flag{
pthread_mutex_lock(&_pLock);
sleep(1);
if (flag > 0) {
NSLog(@"%@請(qǐng)求%d", [NSThread currentThread], flag);
[self recursive:(--flag)];
}else{
flag--;
NSLog(@"%@請(qǐng)求成功", [NSThread currentThread]);
}
pthread_mutex_unlock(&_pLock);
NSLog(@"遞歸解鎖%d", flag + 1);
}
// 輸出結(jié)果
15:02:17.337545+0800 threadTest[1096:242086] <NSThread: 0x281e7a0c0>{number = 3, name = (null)}請(qǐng)求2
15:02:18.343244+0800 threadTest[1096:242086] <NSThread: 0x281e7a0c0>{number = 3, name = (null)}請(qǐng)求1
15:02:19.348308+0800 threadTest[1096:242086] <NSThread: 0x281e7a0c0>{number = 3, name = (null)}請(qǐng)求成功
15:02:19.348655+0800 threadTest[1096:242086] 遞歸解鎖0
15:02:19.348980+0800 threadTest[1096:242086] 遞歸解鎖1
15:02:19.349154+0800 threadTest[1096:242086] 遞歸解鎖2
15:02:20.355346+0800 threadTest[1096:242091] <NSThread: 0x281e468c0>{number = 4, name = (null)}請(qǐng)求2
15:02:21.356200+0800 threadTest[1096:242091] <NSThread: 0x281e468c0>{number = 4, name = (null)}請(qǐng)求1
15:02:22.361770+0800 threadTest[1096:242091] <NSThread: 0x281e468c0>{number = 4, name = (null)}請(qǐng)求成功
15:02:22.361986+0800 threadTest[1096:242091] 遞歸解鎖0
15:02:22.362130+0800 threadTest[1096:242091] 遞歸解鎖1
15:02:22.362502+0800 threadTest[1096:242091] 遞歸解鎖2
6.NSLock(互斥鎖)
NSLock 是 Objective-C 以對(duì)象的形式暴露給開發(fā)者的一種鎖,只是在內(nèi)部封裝了一個(gè) pthread_mutex,屬性為 PTHREAD_MUTEX_ERRORCHECK,它會(huì)損失一定性能換來錯(cuò)誤提示。NSLock 比 pthread_mutex 略慢的原因在于它需要經(jīng)過方法調(diào)用,同時(shí)由于緩存的存在,多次方法調(diào)用不會(huì)對(duì)性能產(chǎn)生太大的影響。
測(cè)試代碼和OSSpinLock相同 替換對(duì)應(yīng)上鎖解鎖代碼
// 初始化
NSLock *lock = [[NSLock alloc] init];
[lock lock];// 加鎖
[lock unlock];// 解鎖
// 嘗試加鎖,可以加鎖則立即加鎖并返回 YES,反之返回 NO
[lock tryLock];
// 這個(gè)方法表示會(huì)在傳入的時(shí)間內(nèi)嘗試加鎖,若能加鎖則執(zhí)行加鎖操作并返回 YES,反之返回 NO
[lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
7. NSCondition(條件鎖)
_condition = [[NSCondition alloc] init];// 初始化
[_condition lock];// 加鎖
[_condition unlock];// 解鎖
[_condition wait];// 進(jìn)入等待狀態(tài)
[_condition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];// 讓一個(gè)線程等待一定的時(shí)間
[_condition signal];// 喚醒一個(gè)等待的線程
[_condition broadcast];// 喚醒所有等待的線程
使用NSCondition模擬售票,并增加補(bǔ)充車票功能
BOOL adding;// 是否正在補(bǔ)充車票
BOOL flag;// 是否還能補(bǔ)充車票
----------------------------------------------
//設(shè)置票數(shù)
self.totalCount = 2;
adding = NO;
flag = YES;
// 初始化
_condition = [[NSCondition alloc] init];
_condition.name = @"售票";
dispatch_queue_t queue = dispatch_queue_create("saleTicket5", DISPATCH_QUEUE_CONCURRENT);
//售票線程1
dispatch_async(queue, ^{
[NSThread currentThread].name = @"售票線程A";
[self saleTicket5];
});
NSLog(@"-----------------------");
//售票線程2
dispatch_async(queue, ^{
[NSThread currentThread].name = @"售票線程B";
[self saleTicket5];
});
-(void)saleTicket5{
while (1) {
NSLog(@"%@準(zhǔn)備上鎖",[NSThread currentThread].name);
[_condition lock];
NSLog(@"%@已上鎖",[NSThread currentThread].name);
if (self.totalCount > 0) {
self.totalCount --;
NSLog(@"%@賣出去了一張票,還剩下%zd張票", [NSThread currentThread].name,self.totalCount);
[_condition unlock];
NSLog(@"%@解鎖",[NSThread currentThread].name);
}else{
NSLog(@"%@:票已售完,等待補(bǔ)充車票",[NSThread currentThread].name);
if (!adding) {
adding = YES;
dispatch_async(dispatch_queue_create("addTicket5", DISPATCH_QUEUE_CONCURRENT), ^{
[NSThread currentThread].name = @"添加車票線程";
[self addTicket5];
});
}
[_condition wait];// 進(jìn)入等待狀態(tài)
if (self.totalCount > 0) {
NSLog(@"%@:獲取到新車票",[NSThread currentThread].name);
[_condition unlock];
NSLog(@"%@解鎖",[NSThread currentThread].name);
}else{
NSLog(@"%@:票已售完",[NSThread currentThread].name);
[_condition unlock];
NSLog(@"%@解鎖",[NSThread currentThread].name);
break;
}
}
}
}
-(void)addTicket5{
if (self.totalCount <= 0 && flag) {
self.totalCount += 2;
NSLog(@"%@已補(bǔ)充車票",[NSThread currentThread].name);
flag = NO;
}else{
self.totalCount = 0;
NSLog(@"%@無車票補(bǔ)充",[NSThread currentThread].name);
}
[_condition broadcast];
NSLog(@"%@喚醒所有等待售票線程",[NSThread currentThread].name);
adding = NO;
}
輸出結(jié)果
17:05:08.425699+0800 threadTest[1173:260434] -----------------------
17:05:08.430148+0800 threadTest[1173:260571] 售票線程A準(zhǔn)備上鎖
17:05:08.430309+0800 threadTest[1173:260571] 售票線程A已上鎖
17:05:08.430454+0800 threadTest[1173:260571] 售票線程A賣出去了一張票,還剩下1張票
17:05:08.430582+0800 threadTest[1173:260571] 售票線程A解鎖
17:05:08.430703+0800 threadTest[1173:260571] 售票線程A準(zhǔn)備上鎖
17:05:08.430836+0800 threadTest[1173:260571] 售票線程A已上鎖
17:05:08.430968+0800 threadTest[1173:260571] 售票線程A賣出去了一張票,還剩下0張票
17:05:08.431088+0800 threadTest[1173:260571] 售票線程A解鎖
17:05:08.431205+0800 threadTest[1173:260571] 售票線程A準(zhǔn)備上鎖
17:05:08.431324+0800 threadTest[1173:260571] 售票線程A已上鎖
17:05:08.431361+0800 threadTest[1173:260572] 售票線程B準(zhǔn)備上鎖
17:05:08.431445+0800 threadTest[1173:260571] 售票線程A:票已售完,等待補(bǔ)充車票
17:05:08.432165+0800 threadTest[1173:260572] 售票線程B已上鎖
17:05:08.432285+0800 threadTest[1173:260572] 售票線程B:票已售完,等待補(bǔ)充車票
17:05:08.432427+0800 threadTest[1173:260535] 添加車票線程已補(bǔ)充車票
17:05:08.432530+0800 threadTest[1173:260535] 添加車票線程喚醒所有等待售票線程
17:05:08.432629+0800 threadTest[1173:260571] 售票線程A:獲取到新車票
17:05:08.432724+0800 threadTest[1173:260571] 售票線程A解鎖
17:05:08.432788+0800 threadTest[1173:260571] 售票線程A準(zhǔn)備上鎖
17:05:08.432864+0800 threadTest[1173:260571] 售票線程A已上鎖
17:05:08.433021+0800 threadTest[1173:260571] 售票線程A賣出去了一張票,還剩下1張票
17:05:08.433543+0800 threadTest[1173:260571] 售票線程A解鎖
17:05:08.433577+0800 threadTest[1173:260572] 售票線程B:獲取到新車票
17:05:08.436824+0800 threadTest[1173:260572] 售票線程B解鎖
17:05:08.437085+0800 threadTest[1173:260572] 售票線程B準(zhǔn)備上鎖
17:05:08.437181+0800 threadTest[1173:260572] 售票線程B已上鎖
17:05:08.437260+0800 threadTest[1173:260572] 售票線程B賣出去了一張票,還剩下0張票
17:05:08.437408+0800 threadTest[1173:260572] 售票線程B解鎖
17:05:08.437475+0800 threadTest[1173:260572] 售票線程B準(zhǔn)備上鎖
17:05:08.437540+0800 threadTest[1173:260572] 售票線程B已上鎖
17:05:08.437606+0800 threadTest[1173:260572] 售票線程B:票已售完,等待補(bǔ)充車票
17:05:08.433616+0800 threadTest[1173:260571] 售票線程A準(zhǔn)備上鎖
17:05:08.437749+0800 threadTest[1173:260571] 售票線程A已上鎖
17:05:08.437815+0800 threadTest[1173:260571] 售票線程A:票已售完,等待補(bǔ)充車票
17:05:08.438012+0800 threadTest[1173:260535] 添加車票線程無車票補(bǔ)充
17:05:08.438456+0800 threadTest[1173:260535] 添加車票線程喚醒所有等待售票線程
17:05:08.438559+0800 threadTest[1173:260572] 售票線程B:票已售完
17:05:08.438628+0800 threadTest[1173:260572] 售票線程B解鎖
17:05:08.438722+0800 threadTest[1173:260571] 售票線程A:票已售完
17:05:08.438787+0800 threadTest[1173:260571] 售票線程A解鎖
8. NSRecursiveLock(遞歸鎖)
NSRecursiveLock也是通過 pthread_mutex_lock 函數(shù)來實(shí)現(xiàn),NSRecursiveLock 與 NSLock 的區(qū)別在于內(nèi)部封裝的 pthread_mutex_t 對(duì)象的類型不同,NSRecursiveLock的類型為 PTHREAD_MUTEX_RECURSIVE。
測(cè)試代碼和pthread_mutex(recursive) 相同 替換對(duì)應(yīng)上鎖解鎖代碼
NSRecursiveLock *_recursiveLock = [[NSRecursiveLock alloc] init];// 初始化
[_recursiveLock lock];// 加鎖
[_recursiveLock unlock];// 解鎖
[_recursiveLock tryLock];// 嘗試加鎖,可以加鎖則立即加鎖并返回 YES,反之返回 NO
[_recursiveLock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];// 這個(gè)方法表示會(huì)在傳入的時(shí)間內(nèi)嘗試加鎖,若能加鎖則執(zhí)行加鎖操作并返回 YES,反之返回 NO
9. NSConditionLock(條件鎖)
NSConditionLock *_conditionLock = [[NSConditionLock alloc] init];// 初始化
[_conditionLock lock];// 加鎖
[_conditionLock unlock];// 解鎖
[_conditionLock tryLock];// 嘗試加鎖,可以加鎖則立即加鎖并返回 YES,反之返回 NO
-(void)lockWhenCondition:(NSInteger)condition;//條件成立觸發(fā)鎖,會(huì)阻塞當(dāng)前線程
-(BOOL)tryLockWhenCondition:(NSInteger)condition;//嘗試條件成立觸發(fā)鎖
-(void)unlockWithCondition:(NSInteger)condition;//條件成立解鎖
-(BOOL)lockBeforeDate:(NSDate *)limit;//觸發(fā)鎖 在等待時(shí)間之內(nèi),會(huì)阻塞當(dāng)前線程
-(BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;//觸發(fā)鎖 條件成立 并且在等待時(shí)間之內(nèi),會(huì)阻塞當(dāng)前線程
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:1];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lockWhenCondition:1];
NSLog(@"任務(wù)01開始%@",[NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"任務(wù)01完成%@",[NSThread currentThread]);
[lock unlockWithCondition:3];
});
NSLog(@"任務(wù)01等待完成%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// if ([lock lockWhenCondition:2]) {
if ([lock lockWhenCondition:2 beforeDate:[NSDate dateWithTimeIntervalSinceNow:6]]) {
NSLog(@"任務(wù)02開始%@",[NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"任務(wù)02完成%@",[NSThread currentThread]);
[lock unlockWithCondition:4];
});
NSLog(@"任務(wù)02等待完成%@",[NSThread currentThread]);
}else{
[lock lockWhenCondition:4];
NSLog(@"任務(wù)02開始%@",[NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"任務(wù)02完成%@",[NSThread currentThread]);
[lock unlockWithCondition:5];
});
NSLog(@"任務(wù)02等待完成%@",[NSThread currentThread]);
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// sleep(3);
if ([lock tryLockWhenCondition:3]) {
NSLog(@"任務(wù)03開始%@",[NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"任務(wù)03完成%@",[NSThread currentThread]);
[lock unlockWithCondition:2];
});
NSLog(@"任務(wù)03等待完成%@",[NSThread currentThread]);
}else{
[lock lockWhenCondition:3];
NSLog(@"任務(wù)03失敗%@",[NSThread currentThread]);
[lock unlockWithCondition:4];
}
});
輸出結(jié)果
11:31:32.311166+0800 threadTest[1541:400982] 任務(wù)01開始<NSThread: 0x2832308c0>{number = 3, name = (null)}
11:31:32.311597+0800 threadTest[1541:400982] 任務(wù)01等待完成<NSThread: 0x2832308c0>{number = 3, name = (null)}
11:31:34.497140+0800 threadTest[1541:401005] 任務(wù)01完成<NSThread: 0x283239540>{number = 4, name = (null)}
11:31:34.497599+0800 threadTest[1541:401003] 任務(wù)03失敗<NSThread: 0x283220200>{number = 5, name = (null)}
11:31:38.316952+0800 threadTest[1541:400982] 任務(wù)02開始<NSThread: 0x2832308c0>{number = 3, name = (null)}
11:31:38.317327+0800 threadTest[1541:400982] 任務(wù)02等待完成<NSThread: 0x2832308c0>{number = 3, name = (null)}
11:31:39.372796+0800 threadTest[1541:400982] 任務(wù)02完成<NSThread: 0x2832308c0>{number = 3, name = (null)}
10. @synchronized(條件鎖)
@synchronized是一個(gè) OC 層面的鎖, 主要是通過犧牲性能換來語法上的簡(jiǎn)潔與可讀。我們知道 @synchronized 后面需要緊跟一個(gè) OC 對(duì)象,它實(shí)際上是把這個(gè)對(duì)象當(dāng)做鎖來使用。這是通過一個(gè)哈希表來實(shí)現(xiàn)的,OC 在底層使用了一個(gè)互斥鎖的數(shù)組(你可以理解為鎖池),通過對(duì)對(duì)象去哈希值來得到對(duì)應(yīng)的互斥鎖。
//設(shè)置票數(shù)
self.totalCount = 10;
dispatch_queue_t queue = dispatch_queue_create("saleTicket", DISPATCH_QUEUE_CONCURRENT);
//售票線程1
dispatch_async(queue, ^{
[NSThread currentThread].name = @"售票線程A";
while (1) {
sleep(1);
if (![self saleTicket6]) {
break;
}
}
});
//售票線程2
dispatch_async(queue, ^{
[NSThread currentThread].name = @"售票線程B";
while (1) {
sleep(2);
if (![self saleTicket6]) {
break;
}
}
});
-(BOOL)saleTicket6{
@synchronized(self) {
if (self.totalCount > 0) {
self.totalCount --;
NSLog(@"%@賣出去了一張票,還剩下%zd張票", [NSThread currentThread].name,self.totalCount);
return YES;
}else{
NSLog(@"%@:票已售完",[NSThread currentThread].name);
return NO;
}
}
}
輸出結(jié)果
13:54:39.353828+0800 threadTest[1604:420440] 售票線程A賣出去了一張票,還剩下9張票
13:54:40.354401+0800 threadTest[1604:420478] 售票線程B賣出去了一張票,還剩下8張票
13:54:40.359244+0800 threadTest[1604:420440] 售票線程A賣出去了一張票,還剩下7張票
13:54:41.363755+0800 threadTest[1604:420440] 售票線程A賣出去了一張票,還剩下6張票
13:54:42.354886+0800 threadTest[1604:420478] 售票線程B賣出去了一張票,還剩下5張票
13:54:42.366684+0800 threadTest[1604:420440] 售票線程A賣出去了一張票,還剩下4張票
13:54:43.372029+0800 threadTest[1604:420440] 售票線程A賣出去了一張票,還剩下3張票
13:54:44.360261+0800 threadTest[1604:420478] 售票線程B賣出去了一張票,還剩下2張票
13:54:44.373056+0800 threadTest[1604:420440] 售票線程A賣出去了一張票,還剩下1張票
13:54:45.373976+0800 threadTest[1604:420440] 售票線程A賣出去了一張票,還剩下0張票
13:54:46.364459+0800 threadTest[1604:420478] 售票線程B:票已售完
13:54:46.374672+0800 threadTest[1604:420440] 售票線程A:票已售完
關(guān)于 @synchronized,這兒比你想知道的還要多
11. pthread_rwlock(讀寫鎖)
讀寫鎖是用來解決讀者寫者問題的,讀操作可以共享,寫操作是排他的,讀可以有多個(gè)在讀,寫只有唯一個(gè)在寫,同時(shí)寫的時(shí)候不允許讀。
對(duì)于讀數(shù)據(jù)比修改數(shù)據(jù)頻繁的應(yīng)用,用讀寫鎖代替互斥鎖可以提高效率。因?yàn)槭褂没コ怄i時(shí),即使是讀出數(shù)據(jù)(相當(dāng)于操作臨界區(qū)資源)都要上互斥鎖,而采用讀寫鎖,則可以在任一時(shí)刻允許多個(gè)讀出者存在,提高了更高的并發(fā)度,同時(shí)在某個(gè)寫入者修改數(shù)據(jù)期間保護(hù)該數(shù)據(jù),以免任何其它讀出者或?qū)懭胝叩母蓴_。
//初始化attr
pthread_rwlockattr_t rwlockattr;
pthread_rwlockattr_init(&rwlockattr);
//初始化
pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);
pthread_rwlock_init(&rwlock, &rwlockattr);
//加讀鎖 阻塞線程
pthread_rwlock_rdlock(&rwlock);
//嘗試加讀鎖 不阻塞線程
pthread_rwlock_tryrdlock(&rwlock);
//加寫鎖 阻塞線程
pthread_rwlock_wrlock(&rwlock);
//嘗試加寫鎖 不阻塞線程
pthread_rwlock_trywrlock(&rwlock);
//解鎖
pthread_rwlock_unlock(&rwlock);
//銷毀
pthread_rwlock_destroy(&rwlock);
pthread_rwlockattr_destroy(&rwlockattr);
pthread_rwlock_init(&rwlock, NULL);
_document = @"123";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread currentThread].name = @"線程A";
[self read];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread currentThread].name = @"線程B";
[self write];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread currentThread].name = @"線程C";
[self read];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread currentThread].name = @"線程D";
[self write];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread currentThread].name = @"線程E";
[self read];
});
-(void)read{
NSLog(@"%@準(zhǔn)備查看",[NSThread currentThread].name);
pthread_rwlock_rdlock(&rwlock);
NSLog(@"%@查看內(nèi)容為:%@",[NSThread currentThread].name,_document);
sleep(1);
pthread_rwlock_unlock(&rwlock);
NSLog(@"%@結(jié)束查看",[NSThread currentThread].name);
}
-(void)write{
NSLog(@"%@準(zhǔn)備修改",[NSThread currentThread].name);
pthread_rwlock_wrlock(&rwlock);
_document = [NSString stringWithFormat:@"%@修正為321",[NSThread currentThread].name];
NSLog(@"%@",_document);
sleep(1);
pthread_rwlock_unlock(&rwlock);
NSLog(@"%@完成修改",[NSThread currentThread].name);
}
輸出結(jié)果
14:48:22.750376+0800 threadTest[1623:427484] 線程B準(zhǔn)備修改
14:48:22.750646+0800 threadTest[1623:427484] 線程B修正為321
14:48:22.750827+0800 threadTest[1623:427485] 線程A準(zhǔn)備查看
14:48:22.751105+0800 threadTest[1623:427487] 線程C準(zhǔn)備查看
14:48:22.751798+0800 threadTest[1623:427486] 線程D準(zhǔn)備修改
14:48:22.752112+0800 threadTest[1623:427498] 線程E準(zhǔn)備查看
14:48:23.755845+0800 threadTest[1623:427484] 線程B完成修改
14:48:23.755920+0800 threadTest[1623:427485] 線程A查看內(nèi)容為:線程B修正為321
14:48:23.755941+0800 threadTest[1623:427487] 線程C查看內(nèi)容為:線程B修正為321
14:48:24.760281+0800 threadTest[1623:427485] 線程A結(jié)束查看
14:48:24.760842+0800 threadTest[1623:427487] 線程C結(jié)束查看
14:48:24.761085+0800 threadTest[1623:427486] 線程D修正為321
14:48:25.765181+0800 threadTest[1623:427486] 線程D完成修改
14:48:25.765313+0800 threadTest[1623:427498] 線程E查看內(nèi)容為:線程D修正為321
14:48:26.766249+0800 threadTest[1623:427498] 線程E結(jié)束查看