iOS 加鎖的方式
iOS多執行緒程式設計中,經常碰到多個執行緒訪問共同的一個資源,線上程相互互動的情況下,需要一些同步措施,來保證執行緒之間互動的時候是安全的。下面我們一起看一下學一下iOS的幾種常用的加鎖方式,希望對大家有所幫助!!!
- @synchronized
- NSLock物件鎖
- NSRecursiveLock遞迴鎖
- NSConditionLock條件鎖
- 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 結束
以上就是自己在開發中所經常使用到的加鎖方式,希望對大家有所幫助!!!