本文源自本人的學(xué)習(xí)記錄整理與理解,其中參考閱讀了部分優(yōu)秀的博客和書籍,盡量以通俗簡(jiǎn)單的語句轉(zhuǎn)述。引用到的地方如有遺漏或未能一一列舉原文出處還望見諒與指出,另文章內(nèi)容如有不妥之處還望指教,萬分感謝 !
多線程安全隱患表現(xiàn)在那些方面 ?
資源共享
- 一塊資源可能會(huì)被多個(gè)線程共享,也就是
多個(gè)線程可能會(huì)訪問同一塊資源 - 比如:多個(gè)線程訪問
同一個(gè)對(duì)象、同一個(gè)變量、同一個(gè)文件
當(dāng)多個(gè)線程訪問同一塊資源時(shí),很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題;
多線程數(shù)據(jù)錯(cuò)亂: 顧名思義就是數(shù)據(jù)出現(xiàn)混亂,比如:3條線程在給變量age賦值同時(shí)又有9條線程來取這個(gè)age的值,那么取出來的值就可能會(huì)各不相同;這樣就相當(dāng)于是數(shù)據(jù)出現(xiàn)了錯(cuò)亂 !
多線程數(shù)據(jù)安全:多線程訪問導(dǎo)致了數(shù)據(jù)出現(xiàn)錯(cuò)亂,從而就可能引發(fā)數(shù)據(jù)的安全問題。比如:存取錢時(shí)多條線程同時(shí)操作可能就會(huì)造成錢越取越多或越存越少。
圖解

解決辦法:
- 采用線程同步技術(shù)(同步:協(xié)同步調(diào),按預(yù)定的先后次序進(jìn)行)
- 常見的線程同步技術(shù)是:加鎖

性能從高到低排序
os_unfair_lock 互斥鎖
OSSpinLock 自旋鎖
dispatch_semaphore 信號(hào)量
pthread_mutex 互斥鎖
dispatch_queue(DISPATCH_QUEUE_SERIAL) 串行隊(duì)列
NSLock 普通(互斥)鎖
NSCondition 條件鎖
pthread_mutex(recursive) 遞歸鎖
NSRecursiveLock 遞歸鎖
NSConditionLock 條件鎖
@synchronized 遞歸鎖
推薦使用dispatch_semaphore (ios 4開始)、pthread_mutex、
os_unfair_lock 從 IOS10開始,所以如果老版本不建議使用
OSSpinLock 自旋鎖
使用介紹:
- 需要導(dǎo)入頭文件#import <libkern/OSAtomic.h>

注意: OSSpinLock初始化賦值需要靜態(tài)初始化,應(yīng)該直接賦值;如果是直接一個(gè)方法調(diào)用,用返回值賦值給他是會(huì)報(bào)錯(cuò)。
- 等待鎖的線程會(huì)處于忙等(
busy-wait)狀態(tài),一直占用CPU資源 - 屬于
High-level lock(高級(jí)鎖),特點(diǎn)就是等不到鎖就一直在循環(huán)等待 - 目前已經(jīng)不在安全,可能會(huì)出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn)問題;所以從IOS10開始不推薦使用 !,建議使用
os_unfair_lock- 如果等待鎖的線程優(yōu)先級(jí)較高,它會(huì)一直占用這CPU資源,優(yōu)先級(jí)低的線程就無法釋放鎖 ;
比如:
開啟了thread1(優(yōu)先級(jí)最高)、thread2(優(yōu)先級(jí)最低)這兩條線程來執(zhí)行相同任務(wù),如果thread2先進(jìn)來執(zhí)行,就會(huì)先加鎖準(zhǔn)備執(zhí)行任務(wù);
這時(shí)候thread1剛好進(jìn)來了,發(fā)現(xiàn)線程已經(jīng)被加過鎖了那它只能忙等;忙等相當(dāng)于是在while循環(huán)等待,這也是需要消耗CPU給分配的資源的,由于thread1優(yōu)先級(jí)最高肯定會(huì)分配到更多的資源,這樣可能會(huì)造成thread2沒有資源可被利用無法繼續(xù)執(zhí)行自己的代碼,沒發(fā)繼續(xù)執(zhí)行也就沒辦法解鎖了,thread2家的這把鎖就無法釋放了!
資源搶奪結(jié)果:thread2的無法釋放,thread1一直在忙等;最終就造成死鎖咯 !
解決這種情況需要把忙等改為休眠,就是等待的這個(gè)線程讓他休眠;而這種技術(shù)在os_unfair_lock中實(shí)現(xiàn)了!
os_unfair_lock 互斥鎖的一種
- os_unfair_lock用于取代不安全的OSSpinLock,從IOS10開始才支持
- 從底層調(diào)用看,等待os_unfair_lock鎖的線程會(huì)處于休眠狀態(tài),并非忙等
- 屬于low-level lock(低級(jí)鎖) ,特點(diǎn)等不到鎖就休眠
使用介紹:
- 需要導(dǎo)入頭文件#imorpt <os/lock.h>

dispatch_semaphore 信號(hào)量
- 信號(hào)量的初始值,可以用來控制線程的
并發(fā)訪問的最大數(shù)量;NSOperationQueue的maxConcurrentOperationCount也可以做到 - 信號(hào)量的初始值為1,代表同時(shí)只允許1條線程訪問資源,保證線程同步
- 等待時(shí)進(jìn)行休眠

示例代碼
self.semaphore = dispatch_semaphore_create(5);
- (void)otherTest
{
//開啟20條子線程都來執(zhí)行test方法
for (int i = 0; i < 20; i++) {
[[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
}
}
// 線程10、7、6、9、8
- (void)test
{
// 如果信號(hào)量的值 > 0,就讓信號(hào)量的值減1,然后繼續(xù)往下執(zhí)行代碼
// 如果信號(hào)量的值 <= 0,就會(huì)休眠等待,直到信號(hào)量的值變成>0,就讓信號(hào)量的值減1,然后繼續(xù)往下執(zhí)行代碼
//#define DISPATCH_TIME_NOW (0ull) 不需要等
//#define DISPATCH_TIME_FOREVER (~0ull) 一直等
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"test - %@", [NSThread currentThread]);
// 讓信號(hào)量的值+1
dispatch_semaphore_signal(self.semaphore);
//減一再加一剛好保持不變;
}
如果每個(gè)線程都需要不同的鎖可以用宏定義的方式
#define SemaphoreBegin \
static dispatch_semaphore_t semaphore; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
semaphore = dispatch_semaphore_create(1); \
}); \
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#define SemaphoreEnd \
dispatch_semaphore_signal(semaphore);
pthread_mutex
- pthread開頭的都是跨平臺(tái)在Linux、unix、Windows、IOS都可以使用
- mutex 叫做“互斥鎖”,等待鎖的線程會(huì)處于休眠狀態(tài)
- 此類鎖不用是需要手動(dòng)銷毀的

使用介紹:
- 鎖的屬性值
#define PTHREAD_MUTEX_NORMAL 0 普通互斥鎖
#define PTHREAD_MUTEX_DEFAULT 0 普通互斥鎖
#define PTHREAD_MUTEX_ERRORCHECK 1 檢查錯(cuò)誤互斥鎖
#define PTHREAD_MUTEX_RECURSIVE 2 遞歸鎖(遞歸互斥鎖)
- 需要導(dǎo)入頭文件#import <pthread.h>
普通鎖 PTHREAD_MUTEX_DEFAULT
- (void)__initMutex:(pthread_mutex_t *)mutex
{
// 靜態(tài)初始化
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 動(dòng)態(tài)初始化鎖
pthread_mutex_init(mutex, NULL); //PTHREAD_MUTEX_DEFAULT
}
- (void)__saveMoney
{
//加鎖
pthread_mutex_lock(&_moneyMutex);
[super __saveMoney];
//解鎖
pthread_mutex_unlock(&_moneyMutex);
}
- (void)dealloc
{
//銷毀鎖
pthread_mutex_destroy(&_moneyMutex);
}
遞歸鎖 pthread_mutex(recursive) PTHREAD_MUTEX_RECURSIVE

注意:遞歸鎖允許同一個(gè)線程對(duì)一把鎖進(jìn)行重復(fù)加鎖
條件鎖

- 示例代碼
- (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];
}
// 生產(chǎn)者-消費(fèi)者模式
生產(chǎn)者負(fù)責(zé)生產(chǎn)商品
消費(fèi)者負(fù)責(zé)購(gòu)買
生產(chǎn)者生產(chǎn)出來商品就需要通知消費(fèi)者可以購(gòu)買商品了,在此之前消費(fèi)者可以在休眠等待
// 線程1 消費(fèi)者
// 刪除數(shù)組中的元素
- (void)__remove
{
pthread_mutex_lock(&_mutex);
NSLog(@"__remove - begin");
if (self.data.count == 0) {
// 等待就開始休眠,同時(shí)放開鎖;_cond該參數(shù)是用于將來喚醒的
pthread_cond_wait(&_cond, &_mutex);
}
[self.data removeLastObject];
NSLog(@"刪除了元素");
pthread_mutex_unlock(&_mutex);
}
// 線程2 生產(chǎn)者
// 往數(shù)組中添加元素
- (void)__add
{
pthread_mutex_lock(&_mutex);
sleep(1);
[self.data addObject:@"Test"];
NSLog(@"添加了元素");
// 信號(hào)
pthread_cond_signal(&_cond);
// 廣播
// pthread_cond_broadcast(&_cond);
pthread_mutex_unlock(&_mutex);
}
- (void)dealloc
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
dispatch_queue(DISPATCH_QUEUE_SERIAL)
- 直接使用GCD的串行隊(duì)列,也是可以實(shí)現(xiàn)線程同步的

NSLock、 NSRecursoveLock
-
NSLock是對(duì)mutex普通鎖的一個(gè)OC版本封裝,說白了就是普通互斥鎖! -
NSRecursiveLock也是對(duì)mutex recursove(遞歸鎖)的OC版本封裝,API跟NSLock基本一致
NSLock、 NSRecursoveLock@2x.png
常用方法解讀
/**
嘗試加鎖,如果加上鎖就返回YES,不會(huì)阻塞線程;
*/
- (BOOL)tryLock;
/**
傳入一個(gè)時(shí)間參數(shù)limit,在這個(gè)時(shí)間之前我能夠等到這把鎖放開的話,我就加鎖成功返回YES;
在此之前等不到我就阻塞線程、睡覺;如果時(shí)間到了還是沒有等到鎖被放開,那就加鎖失敗返回NO!
有返回結(jié)果后,代碼就會(huì)往下執(zhí)行
*/
- (BOOL)lockBeforeDate:(NSDate *)limit;
/**
加鎖
*/
- (void)lock;
/**
解鎖
*/
- (void)unlock;
NSCondition
- NSCondition是對(duì)
mutex和cond的OC版本封裝,說白了就是條件鎖

常用方法解讀
//等待
- (void)wait;
//在某個(gè)時(shí)間點(diǎn)之前等待,時(shí)間過了自己就會(huì)結(jié)束休眠
- (BOOL)waitUntilDate:(NSDate *)limit;
//發(fā)部信號(hào)
- (void)signal;
//發(fā)廣播
- (void)broadcast;
- 示例代碼
- (instancetype)init
{
if (self = [super init]) {
self.condition = [[NSCondition alloc] init];
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];
}
// 生產(chǎn)者-消費(fèi)者模式
// 線程1
// 刪除數(shù)組中的元素
- (void)__remove
{
[self.condition lock];
NSLog(@"__remove - begin");
if (self.data.count == 0) {
// 等待
[self.condition wait];
}
[self.data removeLastObject];
NSLog(@"刪除了元素");
[self.condition unlock];
}
// 線程2
// 往數(shù)組中添加元素
- (void)__add
{
[self.condition lock];
sleep(1);
[self.data addObject:@"Test"];
NSLog(@"添加了元素");
// 信號(hào)
[self.condition signal];
// 廣播
// [self.condition broadcast];
[self.condition unlock];
}
NSConditionLock
- NSConditionLock是對(duì)NSCondition的進(jìn)一步封裝,可以設(shè)置具體的
條件值;本質(zhì)上還是條件鎖

可以實(shí)現(xiàn)線程間的依賴,類似NSOperationQueue的依賴;
- (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];
[[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}
//線程__three依賴__two,__two依賴__one
- (void)__one
{
[self.conditionLock lock];
NSLog(@"__one");
sleep(1);
[self.conditionLock unlockWithCondition:2];
}
- (void)__two
{
[self.conditionLock lockWhenCondition:2];
NSLog(@"__two");
sleep(1);
[self.conditionLock unlockWithCondition:3];
}
- (void)__three
{
[self.conditionLock lockWhenCondition:3];
NSLog(@"__three");
[self.conditionLock unlock];
}
@synchronized
- @synchronized是對(duì)mutex遞歸鎖(pthread_mutex(recursive))的封裝
- 源碼查看:objc4中的objc-sync.mm文件
- @synchronized(obj)內(nèi)部會(huì)生成obj對(duì)應(yīng)的遞歸鎖,然后進(jìn)行加鎖、解鎖操作
- 性能差,官方不推薦使用;且編寫時(shí)沒有提示
- 使用起來非常簡(jiǎn)單,是加鎖技術(shù)中最簡(jiǎn)單

- 拿到需要加鎖的對(duì)象,傳入對(duì)象從散列表中取出對(duì)應(yīng)的鎖;
散列表(哈希)
static StripeMap<SyncList> sDataLists
SyncData *data = sDataLists[obj].data
data->mutex.lock()
自旋鎖、互斥鎖比較
- 臨界區(qū):加鎖和解鎖中間的代碼區(qū)域
什么情況使用自旋鎖比較劃算?
- 預(yù)計(jì)線程等待鎖的時(shí)間很短
- 加鎖的代碼(臨界區(qū))經(jīng)常被調(diào)用,但競(jìng)爭(zhēng)情況很少發(fā)生(很少出現(xiàn)多條線程同時(shí)訪問的情況)
- CPU資源不緊張
- 多核處理器
什么情況使用互斥鎖比較劃算?
- 預(yù)計(jì)線程等待鎖的時(shí)間較長(zhǎng)
- 單核處理器
- 臨界區(qū)有IO操作(文件的讀寫操作)
- 臨界區(qū)代碼復(fù)雜或者循環(huán)量大
- 臨界區(qū)競(jìng)爭(zhēng)非常激烈
