目錄ios
多個線程訪問同一塊資源的時候,很容易引起數據混亂問題。 一個你們都喜歡拿來舉例子的就是買票demo
,今天我使用這個案例 假設有100張票,同時開5個窗口買票,5個窗口買票,咱們來看看結果
數組
//賣票演示 - (void)ticketTest{ self.ticketsCount = 50; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); for (NSInteger i = 0; i < 5; i++) { dispatch_async(queue, ^{ for (int i = 0; i < 10; i++) { [self sellingTickets]; } }); } } //賣票 - (void)sellingTickets{ int oldMoney = self.ticketsCount; sleep(.2); oldMoney -= 1; self.ticketsCount = oldMoney; NSLog(@"當前剩餘票數-> %d", oldMoney); } 複製代碼
正常狀況下我有50張票,而後賣了50次,剩餘票數應該是0,可是打印結果居然是3,因此這裏就存在了線程安全問題。安全
出現線程安全的緣由bash
出現線程安全的緣由就是在同一個時間,多個線程同時讀取一個值,像線程A和B同時讀取了當前票數爲10,等因而賣了兩張票,可是總票數其實就減小了一張。服務器
解決方法markdown
使用線程同步技術,按照預約的前後次序依次進行,常見的線程同步技術就是加鎖
併發
自旋鎖(Spin lock)async
自旋鎖與互斥鎖有點相似,只是自旋鎖不會引發調用者睡眠,若是自旋鎖已經被別的執行單元保持,調用者就一直循環在那裏看是 否該自旋鎖的保持者已經釋放了鎖,"自旋"一詞就是所以而得名。其做用是爲了解決某項資源的互斥使用。由於自旋鎖不會引發調用者睡眠,因此自旋鎖的效率遠 高於互斥鎖。雖然它的效率比互斥鎖高,可是它也有些不足之處: 一、自旋鎖一直佔用CPU,他在未得到鎖的狀況下,一直運行--自旋,因此佔用着CPU,若是不能在很短的時 間內得到鎖,這無疑會使CPU效率下降。 二、在用自旋鎖時有可能形成死鎖,當遞歸調用時有可能形成死鎖,調用有些其餘函數也可能形成死鎖,如 copy_to_user()、copy_from_user()、kmalloc()等。 所以咱們要慎重使用自旋鎖,自旋鎖只有在內核可搶佔式或SMP的狀況下才真正須要,在單CPU且不可搶佔式的內核下,自旋鎖的操做爲空操做。自旋鎖適用於鎖使用者保持鎖時間比較短的狀況下。函數
互斥鎖oop
互斥鎖屬於sleep-waiting類型的鎖。例如在一個雙核的機器上有兩個線程(線程A和線程B),它們分別運行在Core0和 Core1上。假設線程A想要經過pthread_mutex_lock操做去獲得一個臨界區的鎖,而此時這個鎖正被線程B所持有,那麼線程A就會被阻塞 (blocking),Core0 會在此時進行上下文切換(Context Switch)將線程A置於等待隊列中,此時Core0就能夠運行其餘的任務(例如另外一個線程C)而沒必要進行忙等待。而自旋鎖則否則,它屬於busy-waiting類型的鎖,若是線程A是使用pthread_spin_lock操做去請求鎖,那麼線程A就會一直在 Core0上進行忙等待並不停的進行鎖請求,直到獲得這個鎖爲止。
兩種鎖的加鎖原理
互斥鎖:線程會從sleep(加鎖)——>running(解鎖),過程當中有上下文的切換,cpu的搶佔,信號的發送等開銷。
自旋鎖:線程一直是running(加鎖——>解鎖),死循環檢測鎖的標誌位,機制不復雜。
對比 互斥鎖的起始原始開銷要高於自旋鎖,可是基本是一勞永逸,臨界區持鎖時間的大小並不會對互斥鎖的開銷形成影響,而自旋鎖是死循環檢測,加鎖全程消耗cpu,起始開銷雖然低於互斥鎖,可是隨着持鎖時間,加鎖的開銷是線性增加。
兩種鎖的應用
互斥鎖用於臨界區持鎖時間比較長的操做,好比下面這些狀況均可以考慮
至於自旋鎖就主要用在臨界區持鎖時間很是短且CPU資源不緊張的狀況下,自旋鎖通常用於多核的服務器。
OSSpinLock叫作」自旋鎖」,使用時須要導入頭文件#import <libkern/OSAtomic.h>
//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//加鎖
OSSpinLockLock(&lock);
//解鎖
OSSpinLockUnlock(&lock);
複製代碼
demo
#import "OSSpinLockDemo.h" #import <libkern/OSAtomic.h> @interface OSSpinLockDemo() @property (assign, nonatomic) OSSpinLock ticketLock; @end @implementation OSSpinLockDemo - (instancetype)init { self = [super init]; if (self) { self.ticketLock = OS_SPINLOCK_INIT; } return self; } //賣票 - (void)sellingTickets{ OSSpinLockLock(&_ticketLock); [super sellingTickets]; OSSpinLockUnlock(&_ticketLock); } @end 複製代碼
OSSpinLock
在iOS10.0之後就被棄用了,可使用os_unfair_lock_lock
替代。並且還有一些安全性問題,具體參考再也不安全的 OSSpinLock
os_unfair_lock
用於取代不安全的OSSpinLock
,從iOS10開始才支持 從底層調用看,等待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);
複製代碼
demo
#import "os_unfair_lockDemo.h" #import <os/lock.h> @interface os_unfair_lockDemo() @property (assign, nonatomic) os_unfair_lock ticketLock; @end @implementation os_unfair_lockDemo - (instancetype)init { self = [super init]; if (self) { self.ticketLock = OS_UNFAIR_LOCK_INIT; } return self; } //賣票 - (void)sellingTickets{ os_unfair_lock_lock(&_ticketLock); [super sellingTickets]; os_unfair_lock_unlock(&_ticketLock); } @end 複製代碼
mutex叫作」互斥鎖」,等待鎖的線程會處於休眠狀態。須要導入頭文件#import <pthread.h> 使用步驟
pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); /* * Mutex type attributes */ #define PTHREAD_MUTEX_NORMAL 0 #define PTHREAD_MUTEX_ERRORCHECK 1 #define PTHREAD_MUTEX_RECURSIVE 2 #define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL 複製代碼
// 初始化鎖
pthread_mutex_init(mutex, &attr);
複製代碼
// 銷燬屬性
pthread_mutexattr_destroy(&attr);
複製代碼
pthread_mutex_lock(&_mutex);
pthread_mutex_unlock(&_mutex);
複製代碼
pthread_mutex_destroy(&_mutex);
複製代碼
備註:咱們能夠不初始化屬性,在傳屬性的時候直接傳NULL
,表示使用默認屬性PTHREAD_MUTEX_NORMAL
。pthread_mutex_init(mutex, NULL);
具體代碼
#import "pthread_mutexDemo.h" #import <pthread.h> @interface pthread_mutexDemo() @property (assign, nonatomic) pthread_mutex_t ticketMutex; @end @implementation pthread_mutexDemo - (instancetype)init { self = [super init]; if (self) { // 初始化屬性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); // 初始化鎖 pthread_mutex_init(&(_ticketMutex), &attr); // 銷燬屬性 pthread_mutexattr_destroy(&attr); } return self; } //賣票 - (void)sellingTickets{ pthread_mutex_lock(&_ticketMutex); [super sellingTickets]; pthread_mutex_unlock(&_ticketMutex); } @end 複製代碼
死鎖 咱們稍微的修改一下代碼
//賣票 - (void)sellingTickets{ pthread_mutex_lock(&_ticketMutex); [super sellingTickets]; [self sellingTickets2]; pthread_mutex_unlock(&_ticketMutex); } - (void)sellingTickets2{ pthread_mutex_lock(&_ticketMutex); NSLog(@"%s",__func__); pthread_mutex_unlock(&_ticketMutex); } 複製代碼
上面的代碼就會形成線程死鎖
,由於方法sellingTickets
的結束須要sellingTickets2
解鎖,方法sellingTickets2
的結束須要sellingTickets
解鎖,相互引用形成死鎖
可是pthread_mutex_t
裏面有一個屬性能夠解決這個問題PTHREAD_MUTEX_RECURSIVE
PTHREAD_MUTEX_RECURSIVE
遞歸鎖:容許同一個線程對同一把鎖進行重複加鎖。要考重點同一個線程
和同一把鎖
- (instancetype)init { self = [super init]; if (self) { // 初始化屬性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 初始化鎖 pthread_mutex_init(&(_ticketMutex), &attr); // 銷燬屬性 pthread_mutexattr_destroy(&attr); } return self; } 複製代碼
對於上面的問題還有一個解決方案就是在方法sellingTickets2
中從新在建立一把新的鎖,兩個方法的鎖對象不一樣,就不會形成線程死鎖了。
條件
// 初始化屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化鎖
pthread_mutex_init(&_mutex, &attr);
// 銷燬屬性
pthread_mutexattr_destroy(&attr);
// 初始化條件
pthread_cond_t condition
pthread_cond_init(&_cond, NULL);
// 等待條件
pthread_cond_wait(&_cond, &_mutex);
//激活一個等待該條件的線程
pthread_cond_signal(&_cond);
//激活全部等待該條件的線程
pthread_cond_broadcast(&_cond);
//銷燬資源
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
複製代碼
使用案例:假設咱們有一個數組,裏面有兩個線程,一個是添加數組,一個是刪除數組,咱們先調用刪除數組,在調用添加數組,可是在數組爲空的時候不調用刪除數組。
#import "pthread_mutexDemo1.h" #import <pthread.h> @interface pthread_mutexDemo1() @property (assign, nonatomic) pthread_mutex_t mutex; @property (assign, nonatomic) pthread_cond_t cond; @property (strong, nonatomic) NSMutableArray *data; @end @implementation pthread_mutexDemo1 - (instancetype)init { if (self = [super init]) { // 初始化屬性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 初始化鎖 pthread_mutex_init(&_mutex, &attr); // 銷燬屬性 pthread_mutexattr_destroy(&attr); // 初始化條件 pthread_cond_init(&_cond, NULL); self.data = [NSMutableArray array]; } return self; } - (void)otherTest { [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start]; [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start]; } // 線程1 // 刪除數組中的元素 - (void)__remove { pthread_mutex_lock(&_mutex); NSLog(@"__remove - begin"); if (self.data.count == 0) { // 等待 pthread_cond_wait(&_cond, &_mutex); } [self.data removeLastObject]; NSLog(@"刪除了元素"); pthread_mutex_unlock(&_mutex); } // 線程2 // 往數組中添加元素 - (void)__add { pthread_mutex_lock(&_mutex); sleep(1); [self.data addObject:@"Test"]; NSLog(@"添加了元素"); // 激活一個等待該條件的線程 pthread_cond_signal(&_cond); pthread_mutex_unlock(&_mutex); } - (void)dealloc { pthread_mutex_destroy(&_mutex); pthread_cond_destroy(&_cond); } 複製代碼
爲了準確測試咱們能夠在__add
中sleep(1)
NSLock是對mutex
普通鎖的封裝。pthread_mutex_init(mutex, NULL);
NSLock 遵循 NSLocking 協議。Lock 方法是加鎖,unlock 是解鎖,tryLock 是嘗試加鎖,若是失敗的話返回 NO,lockBeforeDate: 是在指定Date以前嘗試加鎖,若是在指定時間以前都不能加鎖,則返回NO
@protocol NSLocking - (void)lock; - (void)unlock; @end @interface NSLock : NSObject <NSLocking> { @private void *_priv; } - (BOOL)tryLock; - (BOOL)lockBeforeDate:(NSDate *)limit; @property (nullable, copy) NSString *name @end 複製代碼
使用起來也是十分的簡單
#import "LockDemo.h" @interface LockDemo() @property (strong, nonatomic) NSLock *ticketLock; @end @implementation LockDemo //賣票 - (void)sellingTickets{ [self.ticketLock lock]; [super sellingTickets]; [self.ticketLock unlock]; } @end 複製代碼
NSRecursiveLock是對mutex
遞歸鎖的封裝,API跟NSLock基本一致
#import "RecursiveLockDemo.h" @interface RecursiveLockDemo() @property (nonatomic,strong) NSRecursiveLock *ticketLock; @end @implementation RecursiveLockDemo //賣票 - (void)sellingTickets{ [self.ticketLock lock]; [super sellingTickets]; [self.ticketLock unlock]; } @end 複製代碼
NSCondition是對mutex
和cond
的封裝,更加面向對象,咱們使用起來也更加的方便簡潔
@interface NSCondition : NSObject <NSLocking> { - (void)wait; - (BOOL)waitUntilDate:(NSDate *)limit; - (void)signal; - (void)broadcast; @property (nullable, copy) NSString *name @end 複製代碼
對於上面那個數組操做的案例咱們就能夠變成這個樣子了
// 線程1 // 刪除數組中的元素 - (void)__remove { [self.condition lock]; if (self.data.count == 0) { // 等待 [self.condition wait]; } [self.data removeLastObject]; NSLog(@"刪除了元素"); [self.condition unlock]; } // 線程2 // 往數組中添加元素 - (void)__add { [self.condition lock]; sleep(1); [self.data addObject:@"Test"]; NSLog(@"添加了元素"); // 信號 [self.condition signal]; [self.condition unlock]; } 複製代碼
NSConditionLock是對NSCondition的進一步封裝,能夠設置具體的條件值
@interface NSConditionLock : NSObject <NSLocking> { - (instancetype)initWithCondition:(NSInteger)condition; @property (readonly) NSInteger condition; - (void)lockWhenCondition:(NSInteger)condition; - (BOOL)tryLock; - (BOOL)tryLockWhenCondition:(NSInteger)condition; - (void)unlockWithCondition:(NSInteger)condition; - (BOOL)lockBeforeDate:(NSDate *)limit; - (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit; @property (nullable, copy) NSString *name; @end 複製代碼
裏面有三個經常使用的方法
initWithCondition:
初始化Condition
,而且設置狀態值lockWhenCondition:(NSInteger)condition:
當狀態值爲condition的時候加鎖unlockWithCondition:(NSInteger)condition
當狀態值爲condition的時候解鎖@interface NSConditionLockDemo() @property (strong, nonatomic) NSConditionLock *conditionLock; @end @implementation NSConditionLockDemo - (instancetype)init { if (self = [super init]) { self.conditionLock = [[NSConditionLock alloc] initWithCondition:1]; } return self; } - (void)otherTest { [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start]; [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start]; } - (void)__one { [self.conditionLock lock]; NSLog(@"__one"); sleep(1); [self.conditionLock unlockWithCondition:2]; } - (void)__two { [self.conditionLock lockWhenCondition:2]; NSLog(@"__two"); [self.conditionLock unlockWithCondition:3]; } @end 複製代碼
//表示最多開啓5個線程
dispatch_semaphore_create(5);
// 若是信號量的值 > 0,就讓信號量的值減1,而後繼續往下執行代碼
// 若是信號量的值 <= 0,就會休眠等待,直到信號量的值變成>0,就讓信號量的值減1,而後繼續往下執行代碼
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
// 讓信號量的值+1
dispatch_semaphore_signal(self.semaphore);
複製代碼
@interface dispatch_semaphoreDemo() @property (strong, nonatomic) dispatch_semaphore_t semaphore; @end @implementation dispatch_semaphoreDemo - (instancetype)init { if (self = [super init]) { self.semaphore = dispatch_semaphore_create(1); } return self; } - (void)otherTest { for (int i = 0; i < 20; i++) { [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start]; } } - (void)test { // 若是信號量的值 > 0,就讓信號量的值減1,而後繼續往下執行代碼 // 若是信號量的值 <= 0,就會休眠等待,直到信號量的值變成>0,就讓信號量的值減1,而後繼續往下執行代碼 dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); sleep(2); NSLog(@"test - %@", [NSThread currentThread]); // 讓信號量的值+1 dispatch_semaphore_signal(self.semaphore); } @end 複製代碼
咱們在運行代碼打印的時候發現,每隔一秒出現一次打印。雖然咱們同時開啓20個線程,可是一次只能訪問一條線程的資源
使用GCD的串行隊列也能夠實現線程同步的
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ // 追加任務1 for (int i = 0; i < 2; ++i) { NSLog(@"1---%@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ // 追加任務2 for (int i = 0; i < 2; ++i) { NSLog(@"2---%@",[NSThread currentThread]); } }); 複製代碼
@synchronized
是對mutex
遞歸鎖的封裝, @synchronized(obj)
內部會生成obj對應的遞歸鎖,而後進行加鎖、解鎖操做
//賣票
- (void)sellingTickets{
@synchronized ([self class]) {
[super sellingTickets];
}
}
複製代碼
對是實現底層咱們能夠在objc4的objc-sync.mm文件中找到
synchronized
就是在開始和結束的時候調用了objc_sync_enter
&objc_sync_exit
方法。
objc_sync_enter
實現
int objc_sync_enter(id obj) { int result = OBJC_SYNC_SUCCESS; if (obj) { SyncData* data = id2data(obj, ACQUIRE); assert(data); data->mutex.lock(); } else { // @synchronized(nil) does nothing if (DebugNilSync) { _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug"); } objc_sync_nil(); } return result; } 複製代碼
就是根據id2data
方法找到一個data
對象,而後在對data
對象進行mutex.lock()
加鎖操做。咱們點擊進入id2data
方法繼續查找
#define LIST_FOR_OBJ(obj) sDataLists[obj].data static StripedMap<SyncList> sDataLists; 複製代碼
發現獲取data
對象的方法其實就是根據sDataLists[obj].data
這個方法來實現的,也就是一個哈希表。
pthread_rwlock常常用於文件等數據的讀寫操做,須要導入頭文件#import <pthread.h>
iOS中的讀寫安全方案須要注意一下場景
//初始化鎖
pthread_rwlock_t lock;
pthread_rwlock_init(&_lock, NULL);
//讀加鎖
pthread_rwlock_rdlock(&_lock);
//讀嘗試加鎖
pthread_rwlock_trywrlock(&_lock)
//寫加鎖
pthread_rwlock_wrlock(&_lock);
//寫嘗試加鎖
pthread_rwlock_trywrlock(&_lock)
//解鎖
pthread_rwlock_unlock(&_lock);
//銷燬
pthread_rwlock_destroy(&_lock);
複製代碼
#import <pthread.h> @interface pthread_rwlockDemo () @property (assign, nonatomic) pthread_rwlock_t lock; @end @implementation pthread_rwlockDemo - (instancetype)init { self = [super init]; if (self) { // 初始化鎖 pthread_rwlock_init(&_lock, NULL); } return self; } - (void)otherTest{ dispatch_queue_t queue = dispatch_get_global_queue(0, 0); for (int i = 0; i < 10; i++) { dispatch_async(queue, ^{ [self read]; }); dispatch_async(queue, ^{ [self write]; }); } } - (void)read { pthread_rwlock_rdlock(&_lock); sleep(1); NSLog(@"%s", __func__); pthread_rwlock_unlock(&_lock); } - (void)write { pthread_rwlock_wrlock(&_lock); sleep(1); NSLog(@"%s", __func__); pthread_rwlock_unlock(&_lock); } - (void)dealloc { pthread_rwlock_destroy(&_lock); } @end 複製代碼
咱們能夠發現讀操做1s有可能出現屢次,可是寫操做不會
這個函數傳入的併發隊列必須是本身經過dispatch_queue_cretate建立的 若是傳入的是一個串行或是一個全局的併發隊列,那這個函數便等同於dispatch_async函數的效果
//初始化 self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT); //讀操做 dispatch_async(self.queue, ^{ }); //寫操做 dispatch_barrier_async(self.queue, ^{ }); 複製代碼
性能從高到低排序