IOS編碼中,鎖的出現(xiàn)其實是因為多線程安全的問題。那么,問題來了,什么是線程安全?為什么鎖可以解決線程安全問題?單線程是不是絕對的線程安全?iOS編程有多少種鎖?加解鎖的效率如何?......

什么線程安全?
多線程操作共享數(shù)據(jù)不會出現(xiàn)想不到的結(jié)果就是線程安全的,否則,是線程不安全的。
NSInteger total = 0;
- (void)threadNotSafe {
for (NSInteger index = 0; index < 3; index++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
total += 1;
NSLog(@"total: %ld", total);
total -= 1;
NSLog(@"total: %ld", total);
});
}
}
//第一次輸出:
2017-11-28 23:34:11.551570+0800 BasicDemo[75679:5312246] total: 1
2017-11-28 23:34:11.551619+0800 BasicDemo[75679:5312248] total: 3
2017-11-28 23:34:11.551618+0800 BasicDemo[75679:5312249] total: 2
2017-11-28 23:34:11.552120+0800 BasicDemo[75679:5312246] total: 2
2017-11-28 23:34:11.552143+0800 BasicDemo[75679:5312248] total: 1
2017-11-28 23:34:11.552171+0800 BasicDemo[75679:5312249] total: 0
//第二次輸出
2017-11-28 23:34:55.738947+0800 BasicDemo[75683:5313401] total: 1
2017-11-28 23:34:55.738979+0800 BasicDemo[75683:5313403] total: 2
2017-11-28 23:34:55.738985+0800 BasicDemo[75683:5313402] total: 3
2017-11-28 23:34:55.739565+0800 BasicDemo[75683:5313401] total: 2
2017-11-28 23:34:55.739570+0800 BasicDemo[75683:5313402] total: 1
2017-11-28 23:34:55.739577+0800 BasicDemo[75683:5313403] total: 0
NSInteger total = 0;
NSLock *lock = [NSLock new];
- (void)threadSafe {
for (NSInteger index = 0; index < 3; index++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lock];
total += 1;
NSLog(@"total: %ld", total);
total -= 1;
NSLog(@"total: %ld", total);
[lock unlock];
});
}
}
//第一次輸出
2017-11-28 23:35:37.696614+0800 BasicDemo[75696:5314483] total: 1
2017-11-28 23:35:37.696928+0800 BasicDemo[75696:5314483] total: 0
2017-11-28 23:35:37.696971+0800 BasicDemo[75696:5314481] total: 1
2017-11-28 23:35:37.696995+0800 BasicDemo[75696:5314481] total: 0
2017-11-28 23:35:37.697026+0800 BasicDemo[75696:5314482] total: 1
2017-11-28 23:35:37.697050+0800 BasicDemo[75696:5314482] total: 0
//第二次輸出
2017-11-28 23:36:01.790264+0800 BasicDemo[75700:5315159] total: 1
2017-11-28 23:36:01.790617+0800 BasicDemo[75700:5315159] total: 0
2017-11-28 23:36:01.790668+0800 BasicDemo[75700:5315161] total: 1
2017-11-28 23:36:01.790687+0800 BasicDemo[75700:5315161] total: 0
2017-11-28 23:36:01.790711+0800 BasicDemo[75700:5315160] total: 1
2017-11-28 23:36:01.790735+0800 BasicDemo[75700:5315160] total: 0
第一個函數(shù)和第一次和第二次調(diào)用的函數(shù)不一樣,換句話說,不能夠確認(rèn)代碼的執(zhí)行順序和結(jié)果,是線程不安全的;第二個函數(shù)的第一次和第二次輸出結(jié)果是一樣的,可以確定函數(shù)的執(zhí)行結(jié)果,是線程安全的。
鎖
線程不安全是有多線程訪問造成的,那么如何解決?
- 既然線程安全問題是由多線程引起的,那么最極端的做法使用單線程保證線程安全。
- 線程安全是由于多線程訪問修改共享資源引起不可預(yù)測的結(jié)果,因此,如果都是訪問共享資源而不去修改共享資源也可以保證線程安全,比如:設(shè)置屬性的全局變量。
- 使用鎖。
自旋鎖(OSSpinLock)
#pragma mark - 自旋鎖,已經(jīng)被廢棄
- (void)ossPinLockConfig{
osslock = OS_SPINLOCK_INIT;
tickets = 5;
// 線程1
dispatch_queue_t diapatchQueue0 =dispatch_queue_create("diapatchQueue0", DISPATCH_QUEUE_PRIORITY_DEFAULT);
dispatch_async(diapatchQueue0, ^{
[self saleTickets];
});
//線程2
dispatch_queue_t diapatchQueue1 =dispatch_queue_create("diapatchQueue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(diapatchQueue1, ^{
[self saleTickets];
});
}
- (void)saleTickets {
while (1) {
[NSThread sleepForTimeInterval:1];
//加鎖
OSSpinLockLock(&osslock);
if (tickets > 0) {
tickets--;
NSLog(@"剩余票數(shù)= %ld, Thread:%@",tickets,[NSThread currentThread]);
} else {
NSLog(@"票賣完了 Thread:%@",[NSThread currentThread]);
break;
}
//解鎖
OSSpinLockUnlock(&osslock);
}
}
輸出結(jié)果
2018-01-02 16:11:15.727776+0800 LockTest[3136:239407] 剩余票數(shù)= 4, Thread:<NSThread: 0x60c000261680>{number = 3, name = (null)}
2018-01-02 16:11:15.727970+0800 LockTest[3136:239404] 剩余票數(shù)= 3, Thread:<NSThread: 0x60000007f100>{number = 4, name = (null)}
2018-01-02 16:11:16.732482+0800 LockTest[3136:239407] 剩余票數(shù)= 2, Thread:<NSThread: 0x60c000261680>{number = 3, name = (null)}
2018-01-02 16:11:16.732632+0800 LockTest[3136:239404] 剩余票數(shù)= 1, Thread:<NSThread: 0x60000007f100>{number = 4, name = (null)}
2018-01-02 16:11:17.735044+0800 LockTest[3136:239407] 剩余票數(shù)= 0, Thread:<NSThread: 0x60c000261680>{number = 3, name = (null)}
2018-01-02 16:11:17.735261+0800 LockTest[3136:239404] 票賣完了 Thread:<NSThread: 0x60000007f100>{number = 4, name = (null)}
自旋鎖面臨的問題不再安全的 OSSpinLock:
OSSpinLock存在的bug:新版iOS 中,系統(tǒng)維護(hù)了 5 個不同的線程優(yōu)先級/QoS: background,utility,default,user-initiated,user-interactive。高優(yōu)先級線程始終會在低優(yōu)先級線程前執(zhí)行,一個線程不會受到比它更低優(yōu)先級線程的干擾。這種線程調(diào)度算法會產(chǎn)生潛在的優(yōu)先級反轉(zhuǎn)問題,從而破壞了 spin lock。
具體來說,如果一個低優(yōu)先級的線程獲得鎖并訪問共享資源,這時一個高優(yōu)先級的線程也嘗試獲得這個鎖,它會處于spin lock 的忙等狀態(tài)從而占用大量CPU。此時低優(yōu)先級線程無法與高優(yōu)先級線程爭奪CPU 時間,從而導(dǎo)致任務(wù)遲遲完不成、無法釋放lock。這并不只是理論上的問題,libobjc 已經(jīng)遇到了很多次這個問題了,于是蘋果的工程師停用了 OSSpinLock。
蘋果工程師 Greg Parker提到,對于這個問題,一種解決方案是用 truly unbounded backoff算法,這能避免livelock問題,但如果系統(tǒng)負(fù)載高時,它仍有可能將高優(yōu)先級的線程阻塞數(shù)十秒之久;另一種方案是使用 handoff lock 算法,這也是libobjc目前正在使用的。鎖的持有者會把線程 ID 保存到鎖內(nèi)部,鎖的等待者會臨時貢獻(xiàn)出它的優(yōu)先級來避免優(yōu)先級反轉(zhuǎn)的問題。理論上這種模式會在比較復(fù)雜的多鎖條件下產(chǎn)生問題,但實踐上目前還一切都好。
libobjc里用的是Mach 內(nèi)核的 thread_switch()然后傳遞了一個 mach thread port 來避免優(yōu)先級反轉(zhuǎn),另外它還用了一個私有的參數(shù)選項,所以開發(fā)者無法自己實現(xiàn)這個鎖。另一方面,由于二進(jìn)制兼容問題,OSSpinLock 也不能有改動。
最終的結(jié)論就是,除非開發(fā)者能保證訪問鎖的線程全部都處于同一優(yōu)先級,否則 iOS 系統(tǒng)中所有類型的自旋鎖都不能再使用了。
模擬OSSpinLock高低優(yōu)先級場景
#pragma mark - 自旋鎖,已經(jīng)被廢棄
- (void)ossPinLockConfig{
osslock = OS_SPINLOCK_INIT;
tickets = 1000;
// 線程1
// dispatch_queue_t diapatchQueue0 =dispatch_queue_create("diapatchQueue0", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t diapatchQueue0 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(diapatchQueue0, ^{
[self saleTickets:@"2"];
});
//線程2
// dispatch_queue_t diapatchQueue1 =dispatch_queue_create("diapatchQueue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t diapatchQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(diapatchQueue1, ^{
[self saleTickets:@"1"];
});
dispatch_queue_t diapatchQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(diapatchQueue2, ^{
[self saleTickets:@"3"];
});
dispatch_queue_t diapatchQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(diapatchQueue3, ^{
[self saleTickets:@"4"];
});
for (NSInteger i = 0; i < 100000000; i ++) {
dispatch_queue_t diapatchQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(diapatchQueue4, ^{
[self saleTickets:@"5"];
});
}
}
- (void)saleTickets:(NSString *)flag {
while (1) {
[NSThread sleepForTimeInterval:1];
//加鎖
OSSpinLockLock(&osslock);
if (tickets > 0) {
tickets--;
// 如果flag 等于1的情況下阻塞線程
if ([flag isEqualToString:@"1"]) {
sleep(4);
}
NSLog(@"剩余票數(shù)= %ld, Thread:%@-flag:%@",tickets,[NSThread currentThread],flag);
} else {
NSLog(@"票賣完了 Thread:%@-flag:%@",[NSThread currentThread],flag);
break;
}
//解鎖
OSSpinLockUnlock(&osslock);
}
}
我們模擬1億個人去訪問同一個資源,其中flag = 1的線程是低優(yōu)先級的線程,在資源訪問的時候,我們又對線程1訪問資源時候做一個線程阻塞4s,觀察CPU Report 中Usage ComParison Other processes 的變化,在阻塞的過程中Other processes占比會猛然增加,說明其余優(yōu)先級高的線程一直處于等待狀態(tài)而不是休眠狀態(tài),激烈地爭奪CPU的資源。
dispatch_semaphore 信號量
GCD中也已經(jīng)提供了一種信號機(jī)制,使用它我們也可以來構(gòu)建一把”鎖”(從本質(zhì)意義上講,信號量與鎖是有區(qū)別)。
dispatch_semaphore_t signal = dispatch_semaphore_create(1); //傳入值必須 >=0, 若傳入為0則阻塞線程并等待timeout,時間到后會執(zhí)行其后的語句
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"線程1 等待ing");
dispatch_semaphore_wait(signal, overTime); //signal 值 -1
NSLog(@"線程1");
dispatch_semaphore_signal(signal); //signal 值 +1
NSLog(@"線程1 發(fā)送信號");
NSLog(@"--------------------------------------------------------");
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"線程2 等待ing");
dispatch_semaphore_wait(signal, overTime);
NSLog(@"線程2");
dispatch_semaphore_signal(signal);
NSLog(@"線程2 發(fā)送信號");
});
dispatch_semaphore_create(1): 傳入值必須 >=0, 若傳入為 0 則阻塞線程并等待timeout,時間到后會執(zhí)行其后的語句
dispatch_semaphore_wait(signal, overTime):可以理解為 lock,會使得 signal 值 -1
dispatch_semaphore_signal(signal):可以理解為 unlock,會使得 signal 值 +1
關(guān)于信號量,我們可以用停車來比喻:
停車場剩余4個車位,那么即使同時來了四輛車也能停的下。如果此時來了五輛車,那么就有一輛需要等待。
信號量的值(signal)就相當(dāng)于剩余車位的數(shù)目,dispatch_semaphore_wait 函數(shù)就相當(dāng)于來了一輛車,dispatch_semaphore_signal 就相當(dāng)于走了一輛車。停車位的剩余數(shù)目在初始化的時候就已經(jīng)指明了(dispatch_semaphore_create(long value)),調(diào)用一次 dispatch_semaphore_signal,剩余的車位就增加一個;調(diào)用一次dispatch_semaphore_wait剩余車位就減少一個;當(dāng)剩余車位為0時,再來車(即調(diào)用 dispatch_semaphore_wait)就只能等待。有可能同時有幾輛車等待一個停車位。有些車主沒有耐心,給自己設(shè)定了一段等待時間,這段時間內(nèi)等不到停車位就走了,如果等到了就開進(jìn)去停車。而有些車主就像把車停在這,所以就一直等下去。
2018-01-02 18:47:21.055284+0800 LockTest[6884:391804] 線程1 等待ing
2018-01-02 18:47:21.055288+0800 LockTest[6884:391802] 線程2 等待ing
2018-01-02 18:47:21.055414+0800 LockTest[6884:391802] 線程2
2018-01-02 18:47:21.055501+0800 LockTest[6884:391802] 線程2 發(fā)送信號
2018-01-02 18:47:21.055507+0800 LockTest[6884:391804] 線程1
2018-01-02 18:47:21.055576+0800 LockTest[6884:391804] 線程1 發(fā)送信號
2018-01-02 18:47:21.056410+0800 LockTest[6884:391804] --------------------------------------------------------
初始信號量大于0
可以發(fā)現(xiàn),因為我們初始化信號量的時候是大于 0 的,所以并沒有阻塞線程,而是直接執(zhí)行了 線程1 線程2。
我們把 信號量初始值改為 0:
dispatch_semaphore_t signal = dispatch_semaphore_create(0);
運行結(jié)果:
2018-01-02 18:50:19.370792+0800 LockTest[6971:396072] 線程1 等待ing
2018-01-02 18:50:19.370792+0800 LockTest[6971:396321] 線程2 等待ing
2018-01-02 18:50:29.371276+0800 LockTest[6971:396321] 線程2
2018-01-02 18:50:29.371276+0800 LockTest[6971:396072] 線程1
2018-01-02 18:50:29.371448+0800 LockTest[6971:396321] 線程2 發(fā)送信號
2018-01-02 18:50:29.371451+0800 LockTest[6971:396072] 線程1 發(fā)送信號
2018-01-02 18:50:29.371584+0800 LockTest[6971:396072] --------------------------------------------------------
互斥鎖
synchronized互斥鎖性能最差,不推薦使用。
@synchronized(這里添加一個OC對象,一般使用self) {
這里寫要加鎖的代碼
}
注意點
1.加鎖的代碼盡量少
2.添加的OC對象必須在多個線程中都是同一對象
3.優(yōu)點是不需要顯式的創(chuàng)建鎖對象,便可以實現(xiàn)鎖的機(jī)制。
4. @synchronized塊會隱式的添加一個異常處理例程來保護(hù)代碼,該處理例程會在異常拋出的時候自動的釋放互斥鎖。所以如果不想讓隱式的異常處理例程帶來額外的開銷,你可以考慮使用鎖對象。
- (void)synchronizedSaleTickets:(NSString *)flag {
while (1) {
@synchronized(self){
[NSThread sleepForTimeInterval:1];
if (tickets > 0) {
tickets--;
NSLog(@"剩余票數(shù)= %ld, Thread:%@-flag:%@",tickets,[NSThread currentThread],flag);
} else {
NSLog(@"票賣完了 Thread:%@-flag:%@",[NSThread currentThread],flag);
break;
}
}
}
}
pthred_mutex 互斥鎖
#import <pthread.h>
- (void)pthread_mutex{
tickets = 20;
// __block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
//1.線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self pthread_mutexSaleTickets:@"1"];
});
//1.線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self pthread_mutexSaleTickets:@"2"];
});
}
- (void)pthread_mutexSaleTickets:(NSString *)flag {
while (1) {
[NSThread sleepForTimeInterval:1];
pthread_mutex_lock(&mutex);
if (tickets > 0) {
tickets--;
NSLog(@"剩余票數(shù)= %ld, Thread:%@-flag:%@",tickets,[NSThread currentThread],flag);
} else {
NSLog(@"票賣完了 Thread:%@-flag:%@",[NSThread currentThread],flag);
break;
}
pthread_mutex_unlock(&mutex);
}
}
2018-01-03 14:18:45.837111+0800 LockTest[1680:165345] 剩余票數(shù)= 19, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.837307+0800 LockTest[1680:165346] 剩余票數(shù)= 18, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.837481+0800 LockTest[1680:165345] 剩余票數(shù)= 17, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.837581+0800 LockTest[1680:165346] 剩余票數(shù)= 16, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.837720+0800 LockTest[1680:165345] 剩余票數(shù)= 15, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.837868+0800 LockTest[1680:165346] 剩余票數(shù)= 14, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.837978+0800 LockTest[1680:165345] 剩余票數(shù)= 13, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.838052+0800 LockTest[1680:165346] 剩余票數(shù)= 12, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.838117+0800 LockTest[1680:165345] 剩余票數(shù)= 11, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.838352+0800 LockTest[1680:165346] 剩余票數(shù)= 10, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.838499+0800 LockTest[1680:165345] 剩余票數(shù)= 9, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.840398+0800 LockTest[1680:165346] 剩余票數(shù)= 8, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.840495+0800 LockTest[1680:165345] 剩余票數(shù)= 7, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.840584+0800 LockTest[1680:165346] 剩余票數(shù)= 6, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.840694+0800 LockTest[1680:165345] 剩余票數(shù)= 5, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.840830+0800 LockTest[1680:165346] 剩余票數(shù)= 4, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.840933+0800 LockTest[1680:165345] 剩余票數(shù)= 3, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.841050+0800 LockTest[1680:165346] 剩余票數(shù)= 2, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.841141+0800 LockTest[1680:165345] 剩余票數(shù)= 1, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.841310+0800 LockTest[1680:165346] 剩余票數(shù)= 0, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.841386+0800 LockTest[1680:165345] 票賣完了 Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
NSLock 普通鎖
lock、unlock:不多做解釋,和上面一樣
trylock:能加鎖返回 YES 并執(zhí)行加鎖操作,相當(dāng)于 lock,反之返回 NO
lockBeforeDate:這個方法表示會在傳入的時間內(nèi)嘗試加鎖,若能加鎖則執(zhí)行加鎖操作并返回 YES,反之返回 NO
- (void)nslock{
tickets = 20;
lock = [[NSLock alloc] init];
//1.線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self nslockSaleTickets:@"1"];
});
//1.線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self nslockSaleTickets:@"2"];
});
}
- (void)nslockSaleTickets:(NSString *)flag {
while (1) {
[NSThread sleepForTimeInterval:1];
[lock lock];
if (tickets > 0) {
tickets--;
NSLog(@"剩余票數(shù)= %ld, Thread:%@-flag:%@",tickets,[NSThread currentThread],flag);
} else {
NSLog(@"票賣完了 Thread:%@-flag:%@",[NSThread currentThread],flag);
break;
}
[lock unlock];
}
}
//線程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"線程2 嘗試加速ing...");
BOOL x = [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];
if (x) {
NSLog(@"線程2");
[lock unlock];
}else{
NSLog(@"失敗");
}
});
NSCondition
wait:進(jìn)入等待狀態(tài)
waitUntilDate::讓一個線程等待一定的時間
signal:喚醒一個等待的線程
broadcast:喚醒所有等待的線程
讓一個線程等待2s
NSCondition *cLock = [NSCondition new];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"start");
[cLock lock];
[cLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
NSLog(@"線程1");
[cLock unlock];
});
waiting 2秒,喚醒一個等待線程
NSCondition *cLock = [NSCondition new];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"線程1加鎖成功");
[cLock wait];
NSLog(@"線程1");
[cLock unlock];
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"線程2加鎖成功");
[cLock wait];
NSLog(@"線程2");
[cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"喚醒一個等待的線程");
[cLock signal];
});
喚醒所有等待的線程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"喚醒所有等待的線程");
[cLock broadcast];
});
NSRecursiveLock 遞歸鎖
遞歸鎖可以被同一線程多次請求,而不會引起死鎖。這主要是用在循環(huán)或遞歸操作中。
NSLock *rLock = [NSLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
[rLock lock];
if (value > 0) {
NSLog(@"線程%d", value);
RecursiveBlock(value - 1);
}
[rLock unlock];
};
RecursiveBlock(4);
});
2018-01-03 14:40:44.206656+0800 LockTest[1816:185465] 線程4
這段代碼是一個典型的死鎖情況。在我們的線程中,RecursiveMethod是遞歸調(diào)用的。所以每次進(jìn)入這個 block時,都會去加一次鎖,而從第二次開始,由于鎖已經(jīng)被使用了且沒有解鎖,所以它需要等待鎖被解除,這樣就導(dǎo)致了死鎖,線程被阻塞住了。
將NSLock 替換為 NSRecursiveLock:
- (void)NSRecursiveLock{
NSRecursiveLock *rLock = [NSRecursiveLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
[rLock lock];
if (value > 0) {
NSLog(@"線程%d", value);
RecursiveBlock(value - 1);
}
[rLock unlock];
};
RecursiveBlock(4);
});
}
2018-01-03 14:42:03.128195+0800 LockTest[1852:187555] 線程4
2018-01-03 14:42:03.128399+0800 LockTest[1852:187555] 線程3
2018-01-03 14:42:03.128618+0800 LockTest[1852:187555] 線程2
2018-01-03 14:42:03.128818+0800 LockTest[1852:187555] 線程1
NSConditionLock 條件鎖
相比于 NSLock多了個 condition參數(shù),我們可以理解為一個條件標(biāo)示。
NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if([cLock tryLockWhenCondition:0]){
NSLog(@"線程1");
[cLock unlockWithCondition:1];
}else{
NSLog(@"失敗");
}
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:3];
NSLog(@"線程2");
[cLock unlockWithCondition:2];
});
//線程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:1];
NSLog(@"線程3");
[cLock unlockWithCondition:3];
});
2018-01-03 14:50:58.893535+0800 LockTest[1892:194965] 線程1
2018-01-03 14:50:58.893826+0800 LockTest[1892:194973] 線程3
2018-01-03 14:50:58.894022+0800 LockTest[1892:194966] 線程2
我們在初始化 NSConditionLock 對象時,給了他的標(biāo)示為 0
執(zhí)行 tryLockWhenCondition:時,我們傳入的條件標(biāo)示也是 0,所 以線程1 加鎖成功
執(zhí)行 unlockWithCondition:時,這時候會把condition由 0 修改為 1
因為condition 修改為了 1, 會先走到 線程3,然后 線程3 又將 condition 修改為 3
最后 走了 線程2 的流程