1亩钟、多線程的安全隱患原因
主要是因?yàn)橘Y源共享。當(dāng)一塊資源可能會(huì)被多個(gè)線程共享時(shí),也就是多個(gè)線程可能會(huì)訪問(wèn)同一塊資源(比如多個(gè)線程訪問(wèn)同一個(gè)對(duì)象话速、同一個(gè)變量讶踪、同一個(gè)文件),很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問(wèn)題泊交。
2乳讥、解決方案
使用線程同步技術(shù)(同步,就是協(xié)同步調(diào)廓俭,按預(yù)定的先后次序進(jìn)行)
常見的線程同步技術(shù)是:加鎖(加完鎖之后記得要解鎖)
gcd的串行隊(duì)列也可以實(shí)現(xiàn)線程同步
以下都是可以保證線程安全的線程同步方案:
OSSpinLock
os_unfair_lock
pthread_mutex
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondition
NSConditionLock
@synchronized
3云石、各種鎖的說(shuō)明
OSSpinLock
OSSpinLockLock是一種自旋鎖(高級(jí)鎖):在等待過(guò)程中不會(huì)睡眠,而是一直處于循環(huán)狀態(tài)研乒、忙等狀態(tài)汹忠,等待過(guò)程中會(huì)占用cpu資源。
不過(guò),現(xiàn)在OSSpinLockLock已經(jīng)不推薦使用了(iOS10廢棄)宽菜。
原因:可能會(huì)出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn)問(wèn)題谣膳。即:如果等待鎖的線程優(yōu)先級(jí)較高,它會(huì)一直占用著CPU資源铅乡,優(yōu)先級(jí)低的線程就無(wú)法釋放鎖继谚。
(假如有兩個(gè)線程執(zhí)行任務(wù),線程1優(yōu)先級(jí)高阵幸,線程2優(yōu)先級(jí)低花履,當(dāng)線程2先執(zhí)行任務(wù)時(shí),OSSpinLock加鎖挚赊,cpu分配更多的資源執(zhí)行線程1诡壁,導(dǎo)致線程1一直加鎖失敗,線程2一直無(wú)法解鎖咬腕。)
//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//嘗試加鎖(如果需要等待就不加鎖欢峰,直接返回false;如果不需要等待就加鎖涨共,返回ture)
bool result = OSSpinLockTry(&lock);
//加鎖
OSSpinLock(&lock);
//解鎖
OSSpinLockUnlock(&lock);
os_unfair_lock
os_unfair_lock用于取代不安全的OSSpinLock纽帖。
os_unfair_lock是一種低級(jí)鎖:在等待過(guò)程中會(huì)睡眠,這樣的鎖在喚起時(shí)候也會(huì)浪費(fèi)很多的資源举反。
//初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//嘗試加鎖,同OSSpinLock
bool result = os_unfair_lock_trylock(&lock);
//加鎖
os_unfair_lock_lock(&lock);
//解鎖
os_unfair_lock_unlock(&lock);
pthread_mutex
-
pthread_mutex是一個(gè)互斥鎖懊直。
當(dāng)設(shè)置屬性為PTHREAD_MUTEX_NORMAL時(shí)候,鎖就是默認(rèn)default為互斥鎖火鼻。
//初始化鎖的屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
//初始化鎖
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
//嘗試加鎖
pthread_mutex_trylock(&mutex);
//加鎖
pthread_mutex_lock(&mutex);
//解鎖
pthread_mutex_unlock(&mutex);
//銷魂相關(guān)屬性
pthread_mutexattr_destroy(&attr);
pthread_mutex_destroy(&mutex);
-
pthread_mutex:遞歸
當(dāng)設(shè)置屬性為PTHREAD_MUTEX_RECURSIVE室囊,為遞歸鎖(屬于互斥鎖的一種特例),允許同一個(gè)線程在未釋放其擁有的鎖時(shí)反復(fù)對(duì)該鎖進(jìn)行加鎖操作魁索。
// 初始化屬性
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_mutex_destroy(mutex);
- pthread_mutex:條件
// 初始化屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
//初始化鎖
pthread_mutex_init(&_mutex, &attr);
//銷毀屬性
pthread_mutexattr_destroy(&attr);
//初始化條件
pthread_cond_t _cond;
pthread_cond_init(&_cond, NULL);
//等待條件(進(jìn)入休眠融撞,放開mutex鎖,被喚醒后粗蔚,會(huì)再次對(duì)mutex加鎖)
pthread_cond_wait(&_cond, &_mutex);
//激活一個(gè)等待該條件的線程
pthread_cond_signal(&_cond);
//激活所有等待該條件的線程
pthread_cond_broadcast(&_cond);
//銷毀資源
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
NSLock尝偎、NSRecursiveLock、NSCondition
NSLock是對(duì)mutex普通鎖的封裝
NSRecursiveLock也是對(duì)mutex遞歸鎖的封裝
NSCondition是對(duì)mutex和cond的封裝
//NSLock的API
NSLock *lock = [[NSLock alloc] init];//初始化鎖
[lock lock];//上鎖
[lock unlock];//解鎖
//NSRecursiveLock的API
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];//初始化鎖
[recursiveLock lock];//上鎖
[recursiveLock unlock];//解鎖
//NSCondition的API
self.condition = [[NSCondition alloc] init];//初始化
[self.condition wait];//等待條件
[self.condition signal];//激活一個(gè)等待該條件的線程
[self.condition broadcast]; //激活所有等待該條件的線程
[self.condition unlock];//解鎖
NSConditionLock
NSConditionLock是對(duì)NSCondition的進(jìn)一步封裝鹏控,可以設(shè)置具體的條件值
//初始化
- (instancetype)initWithCondition:(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;
dispatch_queue (串行serial)
直接使用GCD的串行隊(duì)列致扯,也是可以實(shí)現(xiàn)線程同步的
因?yàn)镚CD的串行隊(duì)列訪問(wèn)同一資源的時(shí)候,本質(zhì)就是一個(gè)一個(gè)按照順序去執(zhí)行的
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
});
dispatch_semaphore
semaphore叫做”信號(hào)量”
信號(hào)量的初始值当辐,可以用來(lái)控制線程并發(fā)訪問(wèn)的最大數(shù)量
信號(hào)量的初始值為1抖僵,代表同時(shí)只允許1條線程訪問(wèn)資源,保證線程同步
//初始化信號(hào)量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(long value);
// 如果信號(hào)量的值 > 0缘揪,就讓信號(hào)量的值減1耍群,然后繼續(xù)往下執(zhí)行代碼
// 如果信號(hào)量的值 <= 0义桂,就會(huì)休眠等待,直到信號(hào)量的值變成>0世吨,就讓信號(hào)量的值減1澡刹,然后繼續(xù)往下執(zhí)行代碼
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//讓信號(hào)量的值+1
dispatch_semaphore_signal(semaphore);
@synchronized
@synchronized是對(duì)mutex遞歸鎖的封裝
@synchronized(obj)內(nèi)部會(huì)生成obj對(duì)應(yīng)的遞歸鎖,然后進(jìn)行加鎖耘婚、解鎖操作
@synchronized (self) {
//任務(wù)
}
以上鎖的使用實(shí)例子見github:https://github.com/ychen3022/lockDemo.git
4罢浇、總結(jié)
- <1>以上各種鎖的性能怎么樣?
os_unfair_lock //性能最好沐祷,但是iOS10才能用嚷闭,是對(duì)os_unfair_lock的替代
OSSpinLock //自旋鎖,不建議使用赖临,已經(jīng)廢棄了
dispatch_semaphore //性能好胞锰,而且ios8、9都可以使用
pthread_mutex //互斥鎖兢榨,推薦使用
dispatch_queue(DISPATCH_QUEUE_SERIAL)//gcd的串行隊(duì)列
NSLock //互斥鎖嗅榕,是對(duì)mutex的封裝
NSCondition//互斥鎖
pthread_mutex(recursive) //遞歸鎖,相比mutext普通鎖吵聪,要耗性能一些
NSRecursiveLock//互斥鎖
NSConditionLock//對(duì)NSCondition的進(jìn)一步封裝凌那,互斥鎖
@synchronized//性能最差,是NSLock的一種封裝吟逝,犧牲了效率帽蝶,簡(jiǎn)潔了語(yǔ)法。
<2>自旋鎖和互斥鎖的對(duì)比块攒?
(1)励稳、自旋鎖:稱之為高級(jí)鎖,會(huì)忙等囱井,不休眠驹尼。即在訪問(wèn)被鎖資源時(shí),調(diào)用者線程不會(huì)休眠庞呕,而是不停循環(huán)在那里新翎,直到被鎖資源釋放鎖。
(2)千扶、互斥鎖:稱之為低級(jí)鎖料祠,會(huì)休眠骆捧。即在訪問(wèn)被鎖資源時(shí)澎羞,調(diào)用者線程會(huì)休眠,此時(shí)cpu可以調(diào)度其他線程工作敛苇。直到被鎖資源釋放鎖妆绞。此時(shí)會(huì)喚醒休眠線程顺呕。什么情況使用自旋鎖比較劃算?
預(yù)計(jì)線程等待鎖的時(shí)間很短
加鎖的代碼(臨界區(qū))經(jīng)常被調(diào)用括饶,但競(jìng)爭(zhēng)情況很少發(fā)生
CPU資源不緊張
多核處理器什么情況使用互斥鎖比較劃算株茶?
預(yù)計(jì)線程等待鎖的時(shí)間較長(zhǎng)
單核處理器
臨界區(qū)有IO(文件讀寫)操作,因?yàn)镮O操作都耗資源
臨界區(qū)代碼復(fù)雜或者循環(huán)量大
臨界區(qū)競(jìng)爭(zhēng)非常激烈