要保證線程安全,就必需要線程同步,而在iOS中線程同步的方案有:html
在iOS中,原子操做能夠保證屬性在單獨的setter
或者getter
方法中是線程安全的,可是不能保證多個線程對同一個屬性進行讀寫操做時,能夠獲得預期的值,也就是原子操做不保證線程安全,例如:ios
// 共享資源name
@property (copy, atomic) NSString *name;
// 初始化
self.name = @"A";
// 線程2進行寫操做,是原子操做,不能夠分割的
self.name = @"B";
// 線程3進行寫操做,是原子操做,不能夠分割的
self.name = @"C";
// 線程4進行讀操做,是原子操做,不能夠分割的,但這時候存在三種可能
self.name == @"A";
self.name == @"B";
self.name == @"C";
複製代碼
在OC中,能夠在設置屬性的時候,使用atomic
來設置原子屬性,保證屬性setter
、getter
的原子性操做,底層是在getter
和setter
內部使用os_unfair_lock
加鎖objective-c
@property (copy, atomic) NSString *name;
複製代碼
在Swift中,原生沒有提供原子操做,可使用DispatchQueue
的同步函數來達到一樣的效果算法
class Person {
// 建立一個隊列
let queue = DispatchQueue(label: "Person")
// 私有化須要原子操做的屬性
private var _name: String = ""
// 向外界暴露的屬性,把它的get和set方法都設置爲同步操做,其實是對_name進行操做,這樣就能夠間接的對name進行原子操做
var name: String {
get {
return queue.sync {
_name
}
}
set {
return queue.sync {
_name = newValue
}
}
}
}
複製代碼
value
,用來控制線程併發訪問的最大數量,當value == 1的時候,就能夠實現線程同步wait()
、 signal()
wait()
:當 value > 0,就將 value 減 1 並立刻返回;當 value == 0,那當前線程就會睡眠,直到其餘線程調用signal()
把 value 加 1,當前線程恢復,而後將value 減1並返回signal()
:將 value 加 1wait()
方法就會立刻掛起當前線程,直到別的線程調用了 signal()
方法,纔會恢復wait()
操做,在另一條線程進行signal()
操做dispatch_semaphore
來使用信號量,也是 GCD 用來同步的一種方式// 初始化一個值爲5的信號量,能夠同時有5條線程訪問臨界區,其餘線程則進入睡眠狀態
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
// wait
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 臨界區...
// signal
dispatch_semaphore_signal(semaphore);
複製代碼
sync
函數就是在當前線程執行任務dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// 臨界區...
});
複製代碼
OSSpinLock
是一種"自旋鎖"。自旋鎖是一種特殊互斥鎖,當一個線程須要獲取自旋鎖時,若是該鎖已經被其餘線程佔用,那麼會一直去請求鎖,進入忙等(busy-waiting)
狀態,因此會一直佔用CPU忙等
狀態從而佔用大量 CPU時間片。此時低優先級線程沒法與高優先級線程爭奪 CPU 時間片,從而致使完成任務而沒法釋放鎖#import <libkern/OSAtomic.h>
OSSpinLock lock = OS_SPINLOCK_INIT;
// 加鎖
OSSpinLockLock(&lock);
// 臨界區...
// 解鎖
OSSpinLockUnlock(&lock);
複製代碼
os_unfair_lock
用於取代不安全的OSSpinLock
,iOS 10開始支持,當一條線程等待鎖的時候會進入睡眠,再也不消耗CPU時間,當其餘線程解鎖之後,操做系統會激活線程os_unfair_lock
有單一的擁有者#import <os/lock.h>
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
// 加鎖
os_unfair_lock_lock(&lock);
// 臨界區...
// 解鎖
os_unfair_lock_unlock(&lock);
複製代碼
pthread
表示POSIX thread
,是POSIX標準的unix多線程庫,定義了一組跨平臺的線程相關的API。pthread_mutex
是一種用 C 語言實現的互斥鎖,有單一的擁有者swift
#import <pthread.h>
// 靜態初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 動態初始化
// 初始化屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化鎖
pthread_mutex_t mutex;
pthread_mutex_init(mutex, &attr);
// 銷燬屬性
pthread_mutexattr_destroy(&attr);
// 加鎖
pthread_mutex_lock(&mutex);
// 臨界區...
// 解鎖
pthread_mutex_unlock(&mutex);
// 銷燬鎖
pthread_mutex_destroy(&_mutex);
複製代碼
NSLock
是以OC對象的形式對pthread_mutex
的封裝,屬性爲 PTHREAD_MUTEX_ERRORCHECK
,它會損失必定性能換來錯誤提示NSLock
比 pthread_mutex
略慢的緣由在於它須要通過方法調用,同時因爲緩存的存在,屢次方法調用不會對性能產生太大的影響NSLock
有單一的擁有者NSLock *lock = [[NSLock alloc] init];
// 加鎖
[lock lock];
// 臨界區...
// 解鎖
[lock unlock];
複製代碼
遞歸鎖是一種特殊互斥鎖。遞歸鎖容許單個線程在釋放以前屢次獲取鎖,其餘線程保持睡眠狀態,直到鎖的全部者釋放鎖的次數與獲取它的次數相同。遞歸鎖主要在遞歸迭代中使用,但也可能在多個方法須要單獨獲取鎖的狀況下使用。緩存
pthread_mutex
支持遞歸鎖,只要把 attr 的類型改爲 PTHREAD_MUTEX_RECURSIVE
便可,它有單一的擁有者安全
#import <pthread.h>
// 初始化屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化鎖
pthread_mutex_t mutex;
pthread_mutex_init(mutex, &attr);
// 銷燬屬性
pthread_mutexattr_destroy(&attr);
// 加鎖
pthread_mutex_lock(&_mutex);
// 臨界區...
// 在同一個線程中能夠屢次獲取鎖
// 解鎖
pthread_mutex_unlock(&_mutex);
// 銷燬鎖
pthread_mutex_destroy(&_mutex);
複製代碼
NSRecursiveLock
是以OC對象的形式對pthread_mutex(Recursive)
的封裝,它有單一的擁有者多線程
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
// 加鎖
[lock lock];
// 臨界區...
// 在同一個線程中能夠屢次獲取鎖
// 解鎖
[lock unlock];
複製代碼
@synchronized
是對pthread_mutex(Recursive)
的封裝,因此它支持遞歸加鎖objc_sync_enter(id obj)
和objc_sync_exit(id obj)
來進行加鎖和解鎖@synchronized
額外還會設置異常處理機制,性能消耗較大@synchronized
有單一的擁有者@synchronized(lock) {
// 臨界區...
}
複製代碼
條件鎖是一種特殊互斥鎖,須要條件變量(condition variable) 來配合。條件變量有點像信號量,提供了線程阻塞與信號機制,所以能夠用來阻塞某個線程,並等待某個數據就緒,隨後喚醒線程。條件鎖是爲了解決生產者-消費者模型
併發
pthread_mutex
配合 pthread_cond_t
,能夠實現條件鎖,其中pthread_cond_t
沒有擁有者app
#import <pthread.h>
// 初始化鎖
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &NULL);
// 銷燬屬性
pthread_mutexattr_destroy(&attr);
// 初始化條件變量
pthread_cond_t cond;
pthread_cond_init(&cond, NULL);
// 消費者
- (void)remove {
// 加鎖
pthread_mutex_lock(&mutex);
// 先判斷某個條件
if (self.data.count == 0) {
// 若是不知足條件,則等待,具體是釋放鎖,用條件變量來阻塞當前線程
// 當條件知足的時候,條件變量喚醒線程,用鎖加鎖
pthread_cond_wait(&cond, &mutex);
}
[self.data removeLastObject];
// 解鎖
pthread_mutex_unlock(&mutex);
}
// 生產者
- (void)add
{
// 加鎖
pthread_mutex_lock(&mutex);
[self.data addObject:@"Test"];
// 信號
// 條件變量喚醒阻塞的線程,用鎖加鎖
pthread_cond_signal(&cond);
// 廣播
// pthread_cond_broadcast(&cond);
// 解鎖
pthread_mutex_unlock(&mutex);
}
// 銷燬
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
複製代碼
NSRecursiveLock
是以OC對象的形式對pthread_mutex
和pthread_cond_t
進行了封裝,NSCondition
沒有擁有者
NSCondition *condition = [[NSCondition alloc] init];
// 消費者
- (void)remove
{
[condition lock];
if (self.data.count == 0) {
// 若是不知足條件,則等待,具體是釋放鎖,用條件變量來阻塞當前線程
// 當條件知足的時候,條件變量喚醒線程,用鎖加鎖
[condition wait];
}
[self.data removeLastObject];
[condition unlock];
}
// 生產者
- (void)add
{
[condition lock];
[self.data addObject:@"Test"];
// 信號
// 條件變量喚醒阻塞的線程,用鎖加鎖
[condition signal];
[condition unlock];
}
複製代碼
NSConditionLock
是對NSCondition
的進一步封裝,能夠設置條件變量的值。經過改變條件變量的值,可使任務之間產生依賴關係,達到使任務按照必定的順序執行,它有單一的擁有者(不肯定)
// 初始化設置條件變量的爲1,若是不設置則默認爲0
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:1];
// 消費者
- (void)remove
{
// 當條件變量爲2的時候加鎖,不然等待
[lock lockWhenCondition:2];
[self.data removeLastObject];
// 直接解鎖
[lock unlock];
}
// 生產者
- (void)add
{
// 直接加鎖
[lock lock];
[self.data addObject:@"Test"];
// 解鎖並讓條件變量爲2
[lock unlockWithCondition:2];
}
複製代碼
讀寫鎖是一種特殊互斥鎖,提供"多讀單寫"的功能,多個線程能夠同時對共享資源進行讀取,可是同一時間只能有一條線程對共享資源進行寫入
pthread_rwlock
有多個擁有者
#import <pthread.h>
// 初始化
pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;
// 讀操做
- (void)read {
pthread_rwlock_rdlock(&lock);
// 臨界區...
pthread_rwlock_unlock(&lock);
}
// 寫操做
- (void)write
{
pthread_rwlock_wrlock(&lock);
// 臨界區...
pthread_rwlock_unlock(&lock);
}
// 銷燬
- (void)dealloc
{
pthread_rwlock_destroy(&lock);
}
複製代碼
dispatch_queue_cretate
建立的,若是傳入的是一個串行或是一個全局的併發隊列,那這個函數便等同於dispatch_async函數的效果dispatch_queue_t queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 讀
});
dispatch_async(queue, ^{
// 讀
});
dispatch_barrier_async(queue, ^{
// 寫
});
dispatch_async(queue, ^{
// 讀
});
複製代碼
性能從高到底分別是:
總結:
OSSpinLock
和os_unfair_lock
性能很高,可是一個是已經廢棄,一個是低級鎖,蘋果不建議使用低級鎖dispatch_semaphore
和pthread_mutex
也具備不錯的性能,NSLock
是pthread_mutex
的封裝,性能上接近NSLock
,而在Swift中使用GCD串行隊列