線程安全與鎖

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,defaultuser-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 ReportUsage 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 的流程

參考文章

iOS 開發(fā)中的八種鎖(Lock)
不再安全的 OSSpinLock
iOS 中幾種常用的鎖總結(jié)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容