1. 程式人生 > >iOS 加鎖的方式

iOS 加鎖的方式

iOS多執行緒程式設計中,經常碰到多個執行緒訪問共同的一個資源,線上程相互互動的情況下,需要一些同步措施,來保證執行緒之間互動的時候是安全的。下面我們一起看一下學一下iOS的幾種常用的加鎖方式,希望對大家有所幫助!!!

  1. @synchronized
  2. NSLock物件鎖
  3. NSRecursiveLock遞迴鎖
  4. NSConditionLock條件鎖
  5. dispatch_semaphore 訊號量實現加鎖(也就是GCD)

介紹與使用

[email protected]

@synchronized關鍵字加鎖,互斥鎖,效能較差不推薦在專案中使用。

@synchronized(這裡新增一個OC物件,一般使用self) {
       這裡寫要加鎖的程式碼
  }注意點
1.加鎖的程式碼要儘量少 2.新增的OC物件必須在多個執行緒中都是同一個物件 3.它的優點是不需要顯式的建立鎖物件,便可以實現鎖的機制。 4. @synchronized塊會隱式的新增異常處理例程來保護程式碼,該處理例程會在異常丟擲的時候就會自動  的釋放互斥鎖。如果不想讓隱式的異常處理例程帶來額外的開銷,你可以考慮使用鎖物件。

下面我們以一個最經典的例子:賣票

//設定票的數量為5
    _tickets = 5;
    
    //執行緒1
    dispatch_async(self.concurrentQueue, ^{
        [self saleTickets];
    });
    
    
//執行緒2 dispatch_async(self.concurrentQueue, ^{ [self saleTickets]; }); - (void)saleTickets { while (1) { @synchronized(self) { [NSThread sleepForTimeInterval:1]; if (_tickets > 0) { _tickets--; NSLog(@"剩餘票數= %ld, Thread:%@
",_tickets,[NSThread currentThread]); } else { NSLog(@"票賣完了 Thread:%@",[NSThread currentThread]); break; } } } }

2.NSLock

基本所有鎖的介面都是通過NSLocking協議定義的,定義了lock和unlock方法,通過這些方法獲取和釋放鎖。

下面還是以賣票的例子講述一下。

//設定票的數量為5
    _tickets = 5;
    
    //建立鎖
    _mutexLock = [[NSLock alloc] init];
    
    //執行緒1
    dispatch_async(self.concurrentQueue, ^{
        [self saleTickets];
    });
    
    //執行緒2
    dispatch_async(self.concurrentQueue, ^{
        [self saleTickets];
    });
 
- (void)saleTickets
{
 
    while (1) {
        [NSThread sleepForTimeInterval:1];
        //加鎖
        [_mutexLock lock];
        if (_tickets > 0) {
            _tickets--;
            NSLog(@"剩餘票數= %ld, Thread:%@",_tickets,[NSThread currentThread]);        
        } else {
            NSLog(@"票賣完了  Thread:%@",[NSThread currentThread]);
            break;
        }
        //解鎖
        [_mutexLock unlock];
    }
}

3.NSRecursiveLock遞迴鎖

使用鎖比較容易犯的錯誤是在遞迴或者迴圈中造成死鎖。

如下程式碼鎖會被多次lock,造成自己被阻塞。

//建立鎖
    _mutexLock = [[NSLock alloc]init];
  
    //執行緒1
    dispatch_async(self.concurrentQueue, ^{
        static void(^TestMethod)(int);
        TestMethod = ^(int value)
        {
            [_mutexLock lock];
            if (value > 0)
            {
                [NSThread sleepForTimeInterval:1];
                TestMethod(value--);
            }
            [_mutexLock unlock];
        };
        
        TestMethod(5);
    });

如果把這個NSLock換成NSRecursiveLock,就可以解決問題。

NSRecursiveLock類定義的鎖,可以在同一執行緒多次lock,不會造成死鎖。

//建立鎖
    _rsLock = [[NSRecursiveLock alloc] init];
    
   //執行緒1
    dispatch_async(self.concurrentQueue, ^{
        static void(^TestMethod)(int);
        TestMethod = ^(int value)
        {
            [_rsLock lock];
            if (value > 0)
            {
                [NSThread sleepForTimeInterval:1];
                TestMethod(value--);
            }
            [_rsLock unlock];
        };
        
        TestMethod(5);
    });

4.NSConditionLock條件鎖

NSMutableArray *products = [NSMutableArray array];  
NSInteger HAS_DATA = 1;  
NSInteger NO_DATA = 0;  
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
    while (1) {  
        [lock lockWhenCondition:NO_DATA];  
        [products addObject:[[NSObject alloc] init]];  
        NSLog(@"produce a product,總量:%zi",products.count);  
        [lock unlockWithCondition:HAS_DATA];  
        sleep(1);  
    }  
});  
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
    while (1) {  
       NSLog(@"wait for product");  
        [lock lockWhenCondition:HAS_DATA];  
       [products removeObjectAtIndex:0];  
       NSLog(@"custome a product");  
       [lock unlockWithCondition:NO_DATA];  
    }  
});

線上程1中的加鎖使用了lock,所以是不要條件的,也就鎖住了。但在unlock的使用整型條件,它可以開啟其他執行緒中正在等待鑰匙的臨界池,當執行緒1迴圈到一次的時候,打開了執行緒2的阻塞。

NSCoditionLock中lock,lockWhenCondition:與unlock,unlockWithCondition:是可以隨意組合的,具體使用根據需求來區分。

5.dispatch_semaphore訊號量實現加鎖

dispatch_semaphore_t signal = dispatch_semaphore_create(1);  
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);  
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
    dispatch_semaphore_wait(signal, overTime);  
            NSLog(@"需要執行緒同步的操作1 開始");  
            sleep(2);  
            NSLog(@"需要執行緒同步的操作1 結束");  
        dispatch_semaphore_signal(signal);  
});  
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
        sleep(1);  
        dispatch_semaphore_wait(signal, overTime);  
            NSLog(@"需要執行緒同步的操作2");  
        dispatch_semaphore_signal(signal);  
});  

dispatch_semaphore是GCD用於同步的方式,與之相關的共有三個函式,dispatch_semaphore_wait,dispatch_semaphore_signal,dispatch_semaphore_create。

(1)dispatch_semaphore_create的宣告為:

dispatch_semaphore_t dispatch_semaphore_create(long value);

傳入的引數是long型別,輸出一個dispatch_semaphore_t型別值為Value的訊號量(value傳入值不能小於0,否則會報錯NULL)

(2)dispatch_semaphore_signal宣告為下面:

long dispatch_semaphore_signal(dispatch_semaphore_t dsema); 

這個方法會使dsema加1;

(3)dispatch_semaphore_wait的宣告為下面:

long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

這個方法會使dsema減1。

整個邏輯如下:

如果dsema訊號量值為大於0,該函式所線上程就會繼續執行下面的語句,並將訊號量的減去1;如果dsema為0時,函式就會阻塞當前的執行緒,如果等待的期間發現dsema的值被dispatch_semaphore_signal加1了,並且該函式得到了訊號量,那麼繼續向下執行,並將訊號量減1,如果等待期間沒有獲得訊號量或者值一直為0,那麼等到timeout,所處的執行緒也會自動執行下面的程式碼。

dispatch_semaphore,當訊號量為1時,可以作為鎖使用。如果沒有出現等待的情況,它的效能比pthread_mutex還要高,當如果有等待情況的時候,效能就會下降很多,相比OSSpinLock(暫不講解),它的優勢在於等待的時侯不會消耗CPU資源。

針對上面程式碼,發現如果超時時間overTime>2,可完成同步操作,反之,線上程1還沒有執行完的情況下,此時超時了,將自動執行下面的程式碼。

上面程式碼執行結果:

2018-09-18 15:40:52.324 SafeMultiThread[35945:579032] 需要執行緒同步的操作1 開始  
2018-09-18 15:40:52.325 SafeMultiThread[35945:579032] 需要執行緒同步的操作1 結束  
2018-09-18 15:40:52.326 SafeMultiThread[35945:579033] 需要執行緒同步的操作2  

如果將overTime<2s的時候,執行為

2018-09-18 15:40:52.049 SafeMultiThread[30834:434334] 需要執行緒同步的操作1 開始  
2018-09-18 15:40:52.554 SafeMultiThread[30834:434332] 需要執行緒同步的操作2  
2018-09-18 15:40:52.054 SafeMultiThread[30834:434334] 需要執行緒同步的操作1 結束  

以上就是自己在開發中所經常使用到的加鎖方式,希望對大家有所幫助!!!