多線程的安全隱患
多條線程操作同一個資源,會造成資源的不同步,造成數(shù)據(jù)的不準確。
采用線程同步技術來規(guī)避資源的同步,常用的技術就是加鎖
鎖
鎖保證當前情況下只能有一個線程操作該資源,操作完解鎖,后面線程進來繼續(xù)加鎖解鎖。
iOS 中線程同步的方案有
-
OSSpinLock(自旋鎖)
等待鎖的線程會處于忙等(busy-wait)狀態(tài),一直占用著CPU資源,
目前已經(jīng)不安全了,會出現(xiàn)線程優(yōu)先級反轉的問題。
優(yōu)先級反轉:
thread1優(yōu)先級高,thread2低,當執(zhí)行任務時,比如thread2加鎖,執(zhí)行thread1會在鎖處忙等,等待thread2解鎖,但是thread1的優(yōu)先級比較高,CPU會分配給thread1的時間調(diào)度比較多,導致thread2一直無法放開鎖,造成類似于死鎖的現(xiàn)象
#import <libkern/OSAtomic.h>
/**
自旋鎖
*/
@property (nonatomic, assign) OSSpinLock lock;
- (void)use {
//初始化
self.lock = OS_SPINLOCK_INIT;
//加鎖
OSSpinLockLock(&_lock);
//解鎖
OSSpinLockUnlock(&_lock);
}
-
os_unfair_lock
os_unfair_lock 用于取代不安全的OSSpinLock,從iOS10 開始支持,從底層來看,等待os_unfair_lock鎖的線程會處于休眠狀態(tài),也不是忙等狀態(tài)。
#import <os/lock.h>
@property (nonatomic, assign) os_unfair_lock lock;
- (void)use {
//初始化
self.lock = OS_UNFAIR_LOCK_INIT;
//加鎖
os_unfair_lock_lock(&_lock);
//解鎖
os_unfair_lock_unlock(&_lock);
}
-
pthread_mutex
pthread是跨平臺的鎖 ,互斥鎖,等待所得線程會處于休眠狀態(tài)。
初始化的時候創(chuàng)建屬性可以選擇鎖類型:
PTHREAD_MUTEX_NORMAL //普通鎖
PTHREAD_MUTEX_RECURSIVE //遞歸鎖,用于遞歸調(diào)用的時候避免產(chǎn)生死鎖情況。
#import <pthread.h>
@property (nonatomic, assign) pthread_mutex_t lock;
- (void)use {
//初始化
[self __initLock:&_lock];
//加鎖
pthread_mutex_lock(&_lock);
//解鎖
pthread_mutex_unlock(&_lock);
//銷毀
pthread_mutex_destroy(&_lock);
}
- (void) __initLock:(pthread_mutex_t *)lock {
//PTHREAD_MUTEX_INITIALIZER是個結構體,語法不允許這么創(chuàng)建
// self.ticketLock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutexattr_t att;
pthread_mutexattr_init(&att);
pthread_mutexattr_settype(&att, PTHREAD_MUTEX_NORMAL);
pthread_mutex_init(lock, &att);
// pthread_mutex_init(lock, NULL); //也可以穿NULL,采用默認
//銷毀屬性
pthread_mutexattr_destroy(&att);
}
還有一種(pthread_mutex-條件)用法,改方法可以實現(xiàn)線程之間相互以依賴的問題,比如線程1加鎖之后,需要依賴線程2執(zhí)行完之后才能夠進行。此時使用pthread_cond_t 可以給pthread_mutex_lock添加條件,來進行多線程之間的控制。

-
NSLock
NSLock是對mutex普通鎖的封裝。
- (BOOL)tryLock; //嘗試加鎖,未加鎖-->加鎖,加鎖-->繼續(xù)執(zhí)行
- (BOOL)lockBeforeDate:(NSDate *)limit;
//時間段內(nèi)加鎖,limit時間之前阻塞線程,等到鎖斷開之后或者未加鎖狀態(tài)下加鎖成功返回YES,否則加鎖失敗 返回NO。
-
NSRecursiveLock
遞歸鎖,也是對mute遞歸鎖的封裝。
pthread_mutexattr_settype(&att, PTHREAD_MUTEX_RECURSIVE);
-
NSCondition
條件鎖,也是對mute和pthread_cond_t的封裝??梢哉{(diào)用wait 方法,等待條件成立再執(zhí)行鎖一下的內(nèi)容。signle方法喚醒線程鎖。
-
NSConditionLock
是對NSCondition進一步封裝,可以設定具體的條件值,首先初始化一個條件值,
當執(zhí)行[self.conditionLock lockWhenCondition:1];只有條件值對應上才能加鎖成功,否則一直等待鎖得狀態(tài)。
[self.conditionLock unlockWithCondition:2];解鎖并且將條件值賦值為2
-
dispatch_semaphore
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
信號量的值>0的時候執(zhí)行,<=0等待
dispatch_semaphore_signal(self.semaphore);
信號量的值+1
-
dispatch_queue(DISPATCH_QUEUE_SERIAL)
GCD的串行隊列,也可以實現(xiàn)鎖的概念。
-
@synchronized
@synchronized是對mutex遞歸鎖得封裝
源碼在objc4/sync.mm 內(nèi)部.
@synchronized(self){
//lock content
}
@synchronized需要傳入一個對象,這個對象(不一定就是self)就是這把鎖,相同對象持有相同鎖,不同對象持有不同鎖,關鍵字在左大括號加鎖,大括號結束解鎖。底層就是對mutex遞歸鎖的封裝。
不同鎖的優(yōu)劣
鎖大致分為2種
- OSSpinLock(自旋鎖),加鎖狀態(tài)的時候線程處于忙等狀態(tài),占用CPU資源。
-
pthread_mutex(互斥鎖),跨平臺,加鎖狀態(tài)的時候線程處于休眠狀態(tài),不會占用CPU資源。
各個鎖性能比較
面試題
- 什么情況使用自旋鎖比較劃算
- 預計線程等待鎖的時間很短
- 加鎖的代碼(臨界區(qū))會經(jīng)常調(diào)用,但競爭情況很少發(fā)生。
- CPU資源不緊張
- 多核處理器
- 什么情況使用互斥鎖比較劃算
- 預計線程等待鎖的時間比較長
- 單核處理器,可以休眠
- 臨界區(qū)有IO(文件讀取)操作
- 臨界區(qū)代碼比較復雜,或者循環(huán)量大
- 競爭非常激烈
