從@property提及(三)atomic與多線程鎖

當咱們寫下@property (weak) id obj時,編譯器默認會給obj這個屬性加atomic關鍵字,也就是說,默認的setter和getter方法裏是加了鎖的。ios

atomic加鎖

在這個系列以前的文章中說過,屬性的attribute關鍵字大部分都是做用在了setter和getter的代碼實現中,像weak和strong是直接做用在了成員變量上,而atomic,nonatomic,copy等等這些關鍵字則是在setter和getter中實現。因此,一旦須要重寫setter和getter方法,那麼在@property中聲明的這幾個關鍵字,固然也就再也不有用了,須要你本身來實現效果,例如@property (nonatomic, copy) NSString *name的setter方法重寫,就須要寫成:算法

- (void)setName:(NSString *)name {
    _name = [name copy];
}

那麼atomic是應該怎麼在setter和getter中實現呢,事實上,僅須要給self加鎖,好比@property (atomic, copy) NSString *name重寫setter,getter:編程

@synthesize name = _name;
- (void)setName:(NSString *)name {
    @synchronized(self) {
    _name = [name copy];
    }
}

- (NSString *)name {
    @synchronized(self) {
        return _name;
    }
}

不過這麼寫,也依然不能保證線程安全。若是線程A調用了getter,同時線程B和線程C都調用了setter,那麼A線程getter獲得的值,多是BC在set以前的原始值,也多是B set的值或者C set的值。同時這個屬性的值,也多是B set的值或者C set的值。
因此,保證數據完整性不能簡單靠一把鎖來完成,畢竟這個是多線程編程最大的難點。swift

雖然一把鎖不能保證數據完整性,可是咱們仍是有必要弄清楚OC究竟均可以用哪些鎖,他們都是什麼特徵。api

多線程加鎖底層知識

1.時間片輪轉調度算法

瞭解多線程加鎖必須知道時間片輪轉調度算法,才能深切理解其原理、性能瓶頸。
現代操做系統在管理普通線程時,一般採用時間片輪轉算法(Round Robin,簡稱 RR)。每一個線程會被分配一段時間片(quantum),一般在 10-100 毫秒左右。當線程用完屬於本身的時間片之後,就會被操做系統掛起,放入等待隊列中,直到下一次被分配時間片,若是線程在時間片結束前阻塞或結束,則CPU立即進行切換。因爲線程切換須要時間,若是時間片過短,會致使大量CPU時間浪費在切換上;而若是這個時間片若是太長,會使得其它線程等待過久。數組

2.原子操做

狹義上的原子操做表示一條不可打斷的操做,也就是說線程在執行操做過程當中,不會被操做系統掛起,而是必定會執行完(理論上擁有CPU時間片無限長)。在單處理器環境下,一條彙編指令顯然是原子操做,由於中斷也要經過指令來實現,但一句高級語言的代碼卻不是原子的,由於它最終是由多條彙編語言完成,CPU在進行時間片切換時,大多都會在某條代碼的執行過程當中。
但在多核處理器下,則須要硬件支持。安全

3.自旋鎖和互斥鎖

都屬於CPU時間分片算法下的實現保護共享資源的一種機制。都實現互斥操做,加鎖後僅容許一個訪問者。
卻別在於自旋鎖不會使線程進入wait狀態,而經過輪訓不停查看是否該自旋鎖的持有者已經釋放的鎖;對應的,互斥鎖在出現鎖已經被佔用的狀況會進入wait狀態,CPU會立即切換時間片。多線程

OC中的同步鎖

1.自旋鎖 OSSpinLock

__block OSSpinLock oslock = OS_SPINLOCK_INIT;
    
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
   NSLog(@"線程2 befor lock");
   OSSpinLockLock(&oslock);
   NSLog(@"線程2");
   OSSpinLockUnlock(&oslock);
   NSLog(@"線程2 unlock");
});
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
   NSLog(@"線程1 befor lock");
   OSSpinLockLock(&oslock);
   NSLog(@"線程1 sleep");
   sleep(4);
   NSLog(@"線程1");
   OSSpinLockUnlock(&oslock);
   NSLog(@"線程1 unlock");
});

OSSpinLock效率奇高,主要緣由是:並無進入系統kernel,使用它能夠節省系統調用和上下文切換。
YY大神在本身的博客中說,OSSpinLock再也不安全(連接:再也不安全的 OSSpinLock
可是在GCD多線程實際使用中,並不會發現什麼問題。並行線程只要獲取oslock,其餘線程一概阻塞。
多線程中每每會遇到另外一個概念:優先級翻轉併發

低優先級線程拿到鎖時,高優先級線程進入忙等(busy-wait)狀態,消耗大量 CPU 時間,從而致使低優先級線程拿不到 CPU 時間,也就沒法完成任務並釋放鎖。這種問題被稱爲優先級反轉。框架

YY大神說OSSpinLock不安全實際上就是由於這個緣由,具體能夠看他的文章。

2.信號量 dispatch_semaphore

YY大神推薦使用信號量dispatch_semaphore做爲自旋鎖的替代方案。

dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 5.0f * NSEC_PER_SEC);
    
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   NSLog(@"線程1 holding");
   dispatch_semaphore_wait(signal, timeout); //signal 值 -1
   NSLog(@"線程1 sleep");
   sleep(4);
   NSLog(@"線程1");
   dispatch_semaphore_signal(signal); //signal 值 +1
   NSLog(@"線程1 post singal");
});
    
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   NSLog(@"線程2 holding");
   dispatch_semaphore_wait(signal, timeout);
   NSLog(@"線程2 sleep");
   sleep(4);
   NSLog(@"線程2");
   dispatch_semaphore_signal(signal);
   NSLog(@"線程2 post signal");
});

dispatch_semaphore_create(1)爲建立信號,()中數字表示能夠同時幾個線程使用信號。爲1表示同步使用。上述代碼若是此處標2就和沒設置信號量同樣,併發自行運行。若是設置爲0,則一概等待overTime時自動釋放,全部代碼都不執行,理論上也具備同步做用。
dispatch_semaphore_wait中傳入的timeout表示最長加鎖時間,自動釋放鎖後,其它線程能夠獲取信號並繼續運行。

3.pthread_mutex鎖

pthread表示的是POSIX thread,定義的是一組跨平臺線程相關的API。
pthread_mutex互斥鎖是一個非遞歸鎖,若是同一線程重複調用加鎖會形成死鎖。
用法比較簡單

static pthread_mutex_t pmutexLock;
pthread_mutex_init(&pLock, NULL);
    
//1.線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
   NSLog(@"線程2 befor lock");
   pthread_mutex_lock(&pLock);
   NSLog(@"線程2");
   pthread_mutex_unlock(&pLock);
});
    
//2.線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   NSLog(@"線程1 before lock");
   pthread_mutex_lock(&pLock);
   sleep(3);
   NSLog(@"線程1");
   pthread_mutex_unlock(&pLock);
});

pthread_mutex(recursive) 遞歸鎖,比較安全,同一線程有且僅有一次加鎖,重複加鎖不會死鎖。不管加鎖幾回,只需解鎖一次。

static pthread_mutex_t pLock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化attr賦初值
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //設置鎖類型爲遞歸鎖
pthread_mutex_init(&pLock, &attr);
pthread_mutexattr_destroy(&attr);
    
//1.線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   static void (^RecursiveBlock)(int);
   RecursiveBlock = ^(int value) {
       pthread_mutex_lock(&pLock);
       if (value > 0) {
           NSLog(@"value: %d", value);
           RecursiveBlock(value - 1);
       }
       
   };
   NSLog(@"線程1 before lock");
   RecursiveBlock(5);
   NSLog(@"線程1");
   pthread_mutex_unlock(&pLock);
   NSLog(@"線程1 unlock");
});
    
//2.線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   NSLog(@"線程2 before lock");
   pthread_mutex_lock(&pLock);
   NSLog(@"線程2");
   pthread_mutex_unlock(&pLock);
   NSLog(@"線程2 unlock");
});

4.Foundation框架NSLock NSRecursiveLock

NS開頭類都是對CoreFoundation的封裝,會更易用。
NSRecursiveLock顧名思義是遞歸鎖。

NSLock 只是在內部封裝了一個 pthread_mutex,屬性爲PTHREAD_MUTEX_ERRORCHECK
NSRecursiveLock 與 NSLock 的區別在於內部封裝的 pthread_mutex_t
對象的類型不一樣,NSRecursiveLock 的類型爲 PTHREAD_MUTEX_RECURSIVE。

5.條件鎖 NSConditionLock

和NSLock主要區別是增長了一個NSInteger類型的condition參數,api很簡單,也不多。condition就是一個條件標識。在加鎖和解鎖時對NSConditionLock作條件判斷和修改,至關於if語句。
實際的實現原理就是裏面封裝了一個NSCondition對象。

NSCondition它一般用於標明共享資源是否可被訪問或者確保一系列任務能按照指定的執行順序執行。若是一個線程試圖訪問一個共享資源,而正在訪問該資源的線程將其條件設置爲不可訪問,那麼該線程會被阻塞,直到正在訪問該資源的線程將訪問條件更改成可訪問狀態或者說給被阻塞的線程發送信號後,被阻塞的線程才能正常訪問這個資源。

NSConditionLock在lock時判斷NSCondition對象的條件是否知足,不知足則wait,unlock時對發送NSCondition的broadcast,屬於一個常見的生產者–消費者模型。

6.簡單易用的@synchronized

@synchronized 其實是把修飾對象當作鎖來使用。這是經過一個哈希表來實現的,OC 在底層使用了一個互斥鎖的數組(你能夠理解爲鎖池),經過對對象去哈希值來獲得對應的互斥鎖。


以上各類鎖的實現原理能夠參考深刻理解 iOS 開發中的鎖
他們的性能能夠參考YY大神的這張圖。
lock_benchmark.png

各類鎖單從效率上來看dispatch_barrier_async和@synchronized差的比較多,不建議使用,其它總體相差不大;相同類型的鎖遞歸鎖和普通鎖效率相差接近一倍,若是不會在循環或者遞歸中頻繁使用加鎖和解鎖,不建議使用遞歸鎖;OSSpinlock各路大神都說有問題,從效率上講,建議用互斥鎖pthread_mutex(YYKit方案)或者信號量dispatch_semaphore(CoreFoundation和protobuf方案)做爲替代。

相關文章
相關標籤/搜索