一薪寓、iOS中常見的多線程方案
1褪那、pthread
- 一套通用的多線程API
- 適用于Unix\Linux\Windows等系統(tǒng)
- 跨平臺(tái)鲤遥、可移植
- 使用難度大
- 使用C語(yǔ)言
- 程序員管理線程生命周期
- 實(shí)際項(xiàng)目中幾乎不用
2斋日、NSThread
- 使用更加面向?qū)ο?/li>
- 簡(jiǎn)單易用饰恕、可直接操作線程對(duì)象
- 使用OC語(yǔ)言
- 程序員管理線程生命周期
- 實(shí)際項(xiàng)目中幾乎不用
3挠羔、GCD
- 旨在替代NSThread技術(shù)
- 充分利用設(shè)備的多核
- 使用C語(yǔ)言
- 自動(dòng)管理線程生命周期
- 實(shí)際項(xiàng)目中經(jīng)常使用
4、NSOperation
- 基于GCD(底層是GCD)
- 比GCD多了一些簡(jiǎn)單實(shí)用的功能
- 使用更加面向?qū)ο?/li>
- 使用OC語(yǔ)言
- 自動(dòng)管理線程生命周期
- 實(shí)際項(xiàng)目中經(jīng)常使用
在項(xiàng)目中更多的時(shí)候我們使用的是GCD埋嵌。
二破加、GCD
1、GCD有兩個(gè)用來執(zhí)行任務(wù)的函數(shù)
- 同步(sync):只能在當(dāng)前線程中執(zhí)行任務(wù)雹嗦,不具備開啟新線程的能力范舀,任務(wù)立刻馬上執(zhí)行,會(huì)阻塞當(dāng)前線程并等待 Block中的任務(wù)執(zhí)行完畢dispatch函數(shù)才會(huì)返回了罪,然后當(dāng)前線程才會(huì)繼續(xù)往下運(yùn)行锭环。
- 異步(async):可以在新的線程中執(zhí)行任務(wù),具備開啟線程的能力泊藕,但不一定會(huì)開啟新的線程田藐,dispatch函數(shù)會(huì)立即返回, 然后Block在后臺(tái)異步執(zhí)行,即當(dāng)前線程會(huì)直接往下執(zhí)行,不會(huì)阻塞當(dāng)前線程汽久。
相關(guān)代碼如下:
#pragma mark - 同步執(zhí)行
- (void)syncQueue {
NSLog(@"同步主線程開始");
//創(chuàng)建串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("com.weixin.globalQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"同步線程");
});
NSLog(@"同步主線程結(jié)束");
}
#pragma mark - 異步執(zhí)行
- (void)asyncQueue {
NSLog(@"異步主線程開始");
dispatch_queue_t queue = dispatch_queue_create("com.qq.globalQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"異步線程");
});
NSLog(@"異步主線程結(jié)束");
}
結(jié)果如下:2鹤竭、隊(duì)列
用于存放任務(wù),分為串行隊(duì)列和并行隊(duì)列景醇。
串行隊(duì)列:所有任務(wù)會(huì)在一條線程中執(zhí)行(有可能是當(dāng)前線程也有可能是新開辟的線程)臀稚,并且一個(gè)任務(wù)執(zhí)行完畢后,才開始執(zhí)行下一個(gè)任務(wù)三痰。
并行隊(duì)列:可以開啟多條線程并行執(zhí)行任務(wù)(但不一定會(huì)開啟新的線程)吧寺,并且當(dāng)一個(gè)任務(wù)放到指定線程開始執(zhí)行時(shí),下一個(gè)任務(wù)就可以開始執(zhí)行了
創(chuàng)建隊(duì)列:
- 串行隊(duì)列
//創(chuàng)建串行隊(duì)列
dispatch_queue_t firstQueue = dispatch_queue_create("com.weibo", DISPATCH_QUEUE_SERIAL);
- 并行隊(duì)列
//創(chuàng)建并行隊(duì)列
dispatch_queue_t secondQueue = dispatch_queue_create("com.facebook", DISPATCH_QUEUE_CONCURRENT);
- 創(chuàng)建全局默認(rèn)并發(fā)隊(duì)列
//創(chuàng)建全局默認(rèn)并發(fā)隊(duì)列
/**
第一個(gè)參數(shù):優(yōu)先級(jí) 也可直接填后面的數(shù)字
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺(tái)
第二個(gè)參數(shù): 預(yù)留參數(shù) 0
*/
dispatch_queue_t queue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- 獲取主隊(duì)列
//獲取主隊(duì)列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
5、任務(wù)隊(duì)列組合
死鎖的問題
使用同步sync函數(shù)向當(dāng)前的串行queue中添加任務(wù)获搏,會(huì)卡住當(dāng)前串行隊(duì)列赖条,產(chǎn)生死鎖。
6常熙、隊(duì)列組
dispatch_group_async & dispatch_group_notify
代碼以及說明如下:
#pragma mark - 隊(duì)列組
- (void)GCDGroup {
//創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
//1.開子線程下載圖片
//創(chuàng)建隊(duì)列(并發(fā))
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//下載圖片1
dispatch_group_async(group, queue, ^{
NSURL *url = [NSURL URLWithString:@"http://www.huabian.com/uploadfile/2015/0914/20150914014032274.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
self.image1 = [UIImage imageWithData:data];
NSLog(@"image1 ==== %@",self.image1);
});
//下載圖片2
dispatch_group_async(group, queue, ^{
NSURL *url = [NSURL URLWithString:@"http://img1.3lian.com/img2011/w12/1202/19/d/88.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
self.image2 = [UIImage imageWithData:data];
NSLog(@"image2 ==== %@",self.image2);
});
//group中所有任務(wù)執(zhí)行完畢纬乍,通知該方法執(zhí)行
dispatch_group_notify(group, queue, ^{
//開啟圖形上下文
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
//畫1
[self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
//畫2
[self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
//根據(jù)圖形上下文拿到圖片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
//關(guān)閉上下文
UIGraphicsEndImageContext();
//回到主線程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"%@--刷新UI",[NSThread currentThread]);
});
});
}
注意:隊(duì)列組一般用來比如進(jìn)入一個(gè)頁(yè)面,同時(shí)異步請(qǐng)求多個(gè)接口裸卫,然后再所有接口請(qǐng)求完畢后仿贬,再返回主線程刷新UI。
三墓贿、面試題
1茧泪、
2、atomic和nonatomic的區(qū)別
給屬性加上atomic修飾聋袋,可以保證屬性的getter和setter方法都是原子性操作(最小單位一個(gè)整體)队伟,也就是說保證setter和getter的內(nèi)部是線程同步的,在內(nèi)部會(huì)在調(diào)用set和get方法前后進(jìn)行加鎖和解鎖的操作舱馅。
但實(shí)際中很少有用到atomic,因?yàn)閷?shí)在太耗費(fèi)性能了刀荒,頻繁的調(diào)用屬性代嗤,頻繁的加鎖解鎖。
四缠借、iOS的各種鎖
多個(gè)線程同時(shí)操作同一塊的資源干毅,會(huì)引起數(shù)據(jù)的錯(cuò)誤混亂
經(jīng)典問題:存儲(chǔ)錢、賣票
解決方案:采用線程同步技術(shù)
常見的線程同步技術(shù):加鎖
1泼返、OSSpinLock自旋鎖
- OSSpinLock叫做“自旋鎖”硝逢,等待鎖的線程會(huì)處于忙等狀態(tài),一直占用著CPU的資源,他不睡覺渠鸽。
- 目前這個(gè)鎖已經(jīng)不安全叫乌,可能出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn)的問題。
- 如果等待鎖的線程優(yōu)先級(jí)高徽缚,它會(huì)一直占用著CPU資源憨奸,優(yōu)先級(jí)低的線程就無法釋放。
- 所以在10.0以后這個(gè)OSSpinLock就被廢棄不再使用了凿试。
2排宰、os_unfair_lock
- 用于取代不安全的OSSpinLink,從iOS10開始支持
- os_unfair_lock是一種互斥鎖那婉,它不會(huì)向自旋鎖那樣忙等板甘,而是等待線程會(huì)休眠
os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;
os_unfair_lock_lock(&unfairLock);
os_unfair_lock_unlock(&unfairLock);
3、pthread_mutex
- 互斥鎖详炬,等待鎖的線程會(huì)處于休眠狀態(tài)
- 跨平臺(tái)的 Unix Linux Windows iOS等都可以使用
- 導(dǎo)入頭文件<pthread.h>
- pthread_mutex(recursive) 遞歸鎖
加鎖后只能有一個(gè)線程訪問該對(duì)象盐类,后面的線程需要排隊(duì),并且 lock 和 unlock 是對(duì)應(yīng)出現(xiàn)的痕寓,同一線程多次 lock 是不允許的傲醉,而遞歸鎖允許同一個(gè)線程在未釋放其擁有的鎖時(shí)反復(fù)對(duì)該鎖進(jìn)行加鎖操作。不用的線程用遞歸鎖呻率,其他線程也要等待unlock才行硬毕。
static pthread_mutex_t pLock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化attr并且給它賦予默認(rèn)
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //設(shè)置鎖類型,這邊是設(shè)置為遞歸鎖
pthread_mutex_init(&pLock, &attr);
pthread_mutexattr_destroy(&attr); //銷毀一個(gè)屬性對(duì)象礼仗,在重新進(jìn)行初始化之前該結(jié)構(gòu)不能重新使用吐咳,初始化完要銷毀
//1.線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
pthread_mutex_lock(&pLock);
if (value > 0) {
NSLog(@"value: %d", value);
RecursiveBlock(value - 1);
}
pthread_mutex_unlock(&pLock);
};
RecursiveBlock(5);
});
4、NSLock
NSLock是對(duì)mutex普通鎖的封裝元践。
@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 API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
tryLock 和 lock 方法都會(huì)請(qǐng)求加鎖韭脊,唯一不同的是trylock在沒有獲得鎖的時(shí)候可以繼續(xù)做一些任務(wù)和處理。lockBeforeDate方法也比較簡(jiǎn)單单旁,就是在limit時(shí)間點(diǎn)之前獲得鎖沪羔,沒有拿到返回NO。
5象浑、NSRecursiveLock
- 遞歸鎖有一個(gè)特點(diǎn)蔫饰,遞歸鎖可以被同一線程多次請(qǐng)求,而不會(huì)引起死鎖愉豺。這主要是用在循環(huán)或遞歸操作中篓吁。
- 是對(duì)pthread_mutex遞歸的封裝
6、NSCondition
- 條件鎖
定義:
是對(duì)pthread_mutex中鎖以及條件的封裝
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
wait:進(jìn)入等待狀態(tài)
waitUntilDate::讓一個(gè)線程等待一定的時(shí)間
signal:?jiǎn)拘岩粋€(gè)等待的線程
broadcast:?jiǎn)拘阉械却木€程
7蚪拦、dispatch_semaphore信號(hào)量
GCD信號(hào)量的應(yīng)用場(chǎng)景杖剪,一般是控制最大并發(fā)量冻押,控制資源的同步訪問,如數(shù)據(jù)訪問盛嘿,網(wǎng)絡(luò)同步加載等洛巢。
用法:
- 創(chuàng)建信號(hào)量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
信號(hào)量的數(shù)量初始化,最好就是使用要執(zhí)行任務(wù)的多少來定孩擂,如題是兩次狼渊,則設(shè)置為2。
- 信號(hào)量-1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
每次執(zhí)行一個(gè)任務(wù)类垦,調(diào)用方法dispatch_semaphore_wait狈邑,讓信號(hào)量減1
如果信號(hào)量的值>0,擇讓信號(hào)量的值減1蚤认,然后繼續(xù)執(zhí)行下面的代碼米苹;
如果信號(hào)量的值<=0,就會(huì)等待休眠砰琢,直到信號(hào)量的值變成>0蘸嘶,擇繼續(xù)減1,然后執(zhí)行下面的代碼
- 信號(hào)量+1
dispatch_semaphore_signal(semaphore);
每次結(jié)束一個(gè)任務(wù)陪汽,調(diào)用方法dispatch_semaphore_signal训唱,讓信號(hào)量+1
注意:
也有一些用法是設(shè)置信號(hào)量為0的,進(jìn)入任務(wù)的時(shí)候signal挚冤,離開任務(wù)的時(shí)候wait况增。
8、@synchronized
實(shí)際項(xiàng)目中:AFNetworking中 isNetworkActivityOccurring屬性的getter方法
- (BOOL)isNetworkActivityOccurring {
@synchronized(self) {
return self.activityCount > 0;
}
}
各種鎖的性能對(duì)比
自旋鎖與互斥鎖的比較