什么是線程不安全和線程安全?
-
線程不安全:
是指不提供加鎖機(jī)制保護(hù),有可能出現(xiàn)多個(gè)線程先后更改數(shù)據(jù)造成所得到的數(shù)據(jù)是臟數(shù)據(jù)。如下圖:
image.png
-
線程安全:
指多個(gè)線程在執(zhí)行同一段代碼的時(shí)候采用加鎖機(jī)制,使每次的執(zhí)行結(jié)果和單線程執(zhí)行的結(jié)果都是一樣的,不存在執(zhí)行程序時(shí)出現(xiàn)意外結(jié)果。如下圖:
image.png
如何解決線程不安全?
使用線程同步技術(shù)(同步,就是協(xié)同步調(diào),按預(yù)定的先后次序進(jìn)行)
常見的線程同步技術(shù)是:加鎖
舉例:
假設(shè)售票系統(tǒng)有15張票,A,B,C同時(shí)來買票,如果是線程不安全,那么可能售票系統(tǒng)可能出現(xiàn)15-1去同時(shí)執(zhí)行的情況,最終結(jié)果是A,B,C都買完后剩下13張票,而不是12張。
/**
賣1張票
*/
- (void)saleTicket
{
int oldTicketsCount = self.ticketsCount;
sleep(.2);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"還剩%d張票 - %@", oldTicketsCount, [NSThread currentThread]);
}
/**
賣票演示
*/
- (void)ticketTest
{
self.ticketsCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
[self saleTicket];
});
dispatch_async(queue, ^{
[self saleTicket];
});
dispatch_async(queue, ^{
[self saleTicket];
});
}
打印結(jié)果為:
2020-03-26 15:29:49.638680+0800 Interview03-安全隱患[12657:281738] 還剩14張票 - <NSThread: 0x600001cd03c0>{number = 9, name = (null)}
2020-03-26 15:29:49.638694+0800 Interview03-安全隱患[12657:281709] 還剩14張票 - <NSThread: 0x600001c9db00>{number = 7, name = (null)}
2020-03-26 15:29:49.638800+0800 Interview03-安全隱患[12657:281458] 還剩13張票 - <NSThread: 0x600001cbaf40>{number = 3, name = (null)}
要解決以上線程不安全的問題,就需要給每個(gè)線程加同一把鎖。
鎖又分為自旋鎖、互斥鎖、遞歸鎖、讀寫鎖
自旋鎖:
自旋鎖是一種特殊的互斥鎖,當(dāng)資源被加鎖后,其它線程想要再次加鎖,此時(shí)該線程不會(huì)被阻塞睡眠而是陷入循環(huán)等待狀態(tài)(不能再做其它事情),循環(huán)檢查資源持有者是否已經(jīng)釋放了資源,這樣做的好處是減少了線程從睡眠到喚醒的資源消耗,但會(huì)一直占用CPU資源。適用于資源的鎖被持有的時(shí)間短,而不希望在線程的喚醒上花費(fèi)太多資源的情況。互斥鎖:
一個(gè)線程獲得資源的使用權(quán)后就會(huì)將改資源加鎖,使用完后會(huì)將其解鎖,所以在使用過程中有其它線程想要獲取該資源的鎖,那么它就會(huì)被阻塞陷入睡眠狀態(tài),直到該資源被解鎖才會(huì)別喚醒,如果被阻塞的資源不止一個(gè),那么它們都會(huì)被喚醒,但是獲得資源使用權(quán)的是第一個(gè)被喚醒的線程,其它線程又陷入沉睡。遞歸鎖:
同一個(gè)線程可以多次獲得該資源鎖,別的線程必須等待該線程釋放所有次數(shù)的鎖才能獲得。讀寫鎖:
讀寫鎖擁有讀狀態(tài)加鎖、寫狀態(tài)加鎖、不加鎖三種狀態(tài)。只有一個(gè)線程可以占有寫狀態(tài)的鎖,但可以多個(gè)線程同時(shí)占有讀狀態(tài)鎖,這也是它可以實(shí)現(xiàn)高并發(fā)的原因。當(dāng)其處于寫狀態(tài)鎖下,任何想要嘗試獲得鎖的線程都會(huì)被阻塞,直到寫狀態(tài)鎖被釋放;如果是處于讀狀態(tài)鎖下,允許其它線程獲得它的讀狀態(tài)鎖,但是不允許獲得它的寫狀態(tài)鎖,當(dāng)讀寫鎖感知到有線程想要獲得寫狀態(tài)鎖時(shí),便會(huì)阻塞其后所有想要獲得讀狀態(tài)鎖的線程。所以讀寫鎖非常適合資源的讀操作遠(yuǎn)多于寫操作的情況。
有以下10種加鎖方案:
注意:在不同的線程中加鎖,必須是同一把鎖。
-
OSSpinLock:需導(dǎo)入頭文件
#import <libkern/OSAtomic.h>,是自旋鎖。
目前已經(jīng)不再安全,可能會(huì)出現(xiàn)優(yōu)先級反轉(zhuǎn)問題。(如果等待鎖的線程的優(yōu)先級較高,它會(huì)一直占用CPU資源,優(yōu)先級低的線程就無法釋放鎖)//初始化鎖 OSSpinLock lock = OS_SPINLOCK_INIT; //加鎖 OSSpinLockLock(&lock); //解鎖 OSSpinLockUnlock(&lock); -
os_unfair_lock:需導(dǎo)入頭文件
#import <os/lock.h>,是互斥鎖。
用于取代不安全的OSSpinLock,從IOS10開始才支持,從底層調(diào)用看,等待os_unfair_lock鎖的線程會(huì)處于休眠狀態(tài),并非忙等。//初始化鎖 os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //加鎖 os_unfair_lock_lock(&lock); //解鎖 os_unfair_lock_unlock(&lock); -
pthread_mutex_t:需導(dǎo)入頭文件
#import <pthread.h>,是互斥鎖。//申明mutex鎖 pthread_mutex_t mutex; //聲明mutex鎖的屬性 pthread_mutexattr_t attr; //初始化屬性attr pthread_mutexattr_init(&attr); //設(shè)置鎖屬性的類型 /* 參數(shù)說明 * * 第一個(gè)參數(shù)pthread_mutexattr_t:mutex鎖屬性attr對象的地址 * 第二個(gè)參數(shù):鎖的類型 #define PTHREAD_MUTEX_NORMAL 0 //默認(rèn)鎖,也就是”互斥鎖“ #define PTHREAD_MUTEX_ERRORCHECK 1 //錯(cuò)誤鎖 #define PTHREAD_MUTEX_RECURSIVE 2 //遞歸鎖 #define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL */ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); //初始化鎖 /* 參數(shù)說明 * * 第一個(gè)參數(shù)pthread_mutex_t:mutex鎖對象的地址 * 第二個(gè)參數(shù)pthread_mutexattr_t:mutex鎖屬性attr對象的地址。該參數(shù)可以傳NULL,為默認(rèn)屬性PTHREAD_MUTEX_DEFAULT,”互斥鎖“ */ pthread_mutex_init(&mutex, &attr); //銷毀mutex鎖的屬性 pthread_mutexattr_destroy(&attr); //加鎖 pthread_mutex_lock(&mutex); //解鎖 pthread_mutex_unlock(&mutex); //銷毀鎖 pthread_mutexattr_destroy(&mutex);擴(kuò)展:
pthread_cond_t條件鎖:配合pthread_mutex_t實(shí)現(xiàn)//初始化條件鎖 pthread_cond_t cond; pthread_cond_init(&cond, NULL); //條件等待,當(dāng)線程調(diào)用該函數(shù),然后阻塞當(dāng)前線程進(jìn)入休眠并解鎖。 pthread_cond_wait(&cond, &mutex); //條件喚醒,當(dāng)線程中調(diào)用該函數(shù),等到該調(diào)用線程解鎖后,然后h就喚醒前面休眠的線程,并給喚醒線程加鎖。 //喚醒一個(gè)等待該條件的線程 pthread_cond_signal(&cond); //喚醒所有等待該條件的線程 pthread_cond_broadcast(&cond); //銷毀條件 pthread_cond_destroy(&cond); -
NSLock:對pthread_mutex_t的鎖封裝。
//初始化鎖 NSLock *lock = [[NSLock alloc] init]; //加鎖 [lock lock]; //解鎖 [lock unlock]; -
NSRecursiveLock::對pthread_mutex_t的的遞歸鎖封裝。
//初始化鎖 NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init]; //加鎖 [recursiveLock lock]; //解鎖 [recursiveLock unlock]; -
NSCondition:對pthread_mutex_t的cond條件和mutex鎖封裝。
//初始化鎖 NSCondition *cond = [[NSCondition alloc] init]; //加鎖 [cond lock]; //條件等待 [cond wait]; //喚醒條件等待 //喚醒一個(gè)條件等待 [cond signal]; //喚醒所有條件等待 [cond broadcast]; //解鎖 [cond unlock]; -
NSConditionLock:對pthread_mutex_的cond條件和mutex鎖封裝。會(huì)根據(jù)內(nèi)部存儲(chǔ)的條件condition的值,進(jìn)行加鎖。
//初始化鎖 NSConditionLock *condLock = [[NSConditionLock alloc] initWithCondition:1]; //加鎖 [condLock lockWhenCondition:1]; //解鎖 [condLock unlockWithCondition:2]; //加鎖 [condLock lockWhenCondition:2]; //解鎖 [condLock unlock]; -
在同步串行隊(duì)列中開啟子線程執(zhí)行任務(wù)。
dispatch_sync(dispatch_queue_create("sertal", DISPATCH_QUEUE_SERIAL), ^{ dispatch_async(dispatch_get_global_queue(0, 0), ^{ //執(zhí)行操作 }); }); -
使用信號量dispatch_semaphore_t控制線程最大并發(fā)數(shù)量:
// 信號量的初始值 int value = 1; //初始化信號量 dispatch_semaphore_t semaphore = dispatch_semaphore_create(value); // 如果信號量的值<=0,當(dāng)前線程就進(jìn)入休眠等待(直到信號量的值>0)。 // 如果信號量的值>0,就減1,然后往下執(zhí)行后面的代碼。 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 讓信號量的值加1 dispatch_semaphore_signal(semaphore); -
@synchronized:是封裝了mutex的遞歸鎖。
@synchronized([self class]) { //執(zhí)行操作 }
IOS線程同步方案性能比較:
從高到低:
os_unfair_lock
OSSpinLock
dispatch_semaphore
pthread_mutex
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSCondition
pthread_mutex(recursive)
NSRecursiveLock
NSConditionLock
@synchronized
多線程文件讀寫安全,多讀單寫:
-
使用讀寫鎖pthread_rwlock_t:是“互斥鎖”。
//聲明讀寫鎖 pthread_rwlock_t rwlock; //初始化讀寫鎖 pthread_rwlock_init(&rwlock, NULL); //讀的加鎖 pthread_rwlock_rdlock(&rwlock); //讀的解鎖 pthread_rwlock_unlock(&rwlock); //寫的加鎖 pthread_rwlock_wrlock(&rwlock); //寫的解鎖 pthread_rwlock_unlock(&rwlock); //銷毀鎖 pthread_rwlock_destroy(&rwlock); -
異步函數(shù)dispatch_async進(jìn)行讀的操作和異步柵欄dispatch_barrier_async進(jìn)行寫的操作:讀寫操作必須保證在同一個(gè)手動(dòng)創(chuàng)建的并發(fā)隊(duì)列中。
//必須手動(dòng)創(chuàng)建并發(fā)隊(duì)列 dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT); //異步h函數(shù)執(zhí)行讀的操作 dispatch_async(queue, ^{ //讀的操作 }); //異步柵欄函數(shù)進(jìn)行寫的操作,隊(duì)列必須是手動(dòng)創(chuàng)建的全局并發(fā)隊(duì)列。 //如果不是,傳入的是串行隊(duì)列或者全局并發(fā)隊(duì)列,異步柵欄會(huì)失效,效果等同于異步函數(shù)dispatch_async dispatch_barrier_async(queue, ^{ //寫的操作 });

