緒
事出必有因冀泻,今天我想和你聊聊線程的原因就是——當(dāng)然是本著一個(gè)Rock Programmer的思想覺悟常侣,為人民透析生命,講解你正在蒙圈的知識(shí)點(diǎn)弹渔,或者想破腦袋才發(fā)現(xiàn)如此簡(jiǎn)單的技術(shù)方案胳施。
很多人學(xué)線程,迷迷糊糊肢专;很多人問線程舞肆,有所期待;也有很多人寫線程鸟召,分享認(rèn)知給正在努力的年輕人胆绊,呦,呦欧募,呦呦压状。但是,你真的了解線程么跟继?你真的會(huì)用多線程么种冬?你真的學(xué)明白,問明白舔糖,寫明白了么娱两?不管你明不明白,反正我不明白金吗,但是十兢,沒準(zhǔn),你看完摇庙,你就明白了旱物。
前言
- 提到線程,那就不得不提CPU卫袒,現(xiàn)代的CPU有一個(gè)很重要的特性宵呛,就是時(shí)間片,每一個(gè)獲得CPU的任務(wù)只能運(yùn)行一個(gè)時(shí)間片規(guī)定的時(shí)間夕凝。
- 其實(shí)線程對(duì)操作系統(tǒng)來(lái)說就是一段代碼以及運(yùn)行時(shí)數(shù)據(jù)宝穗。操作系統(tǒng)會(huì)為每個(gè)線程保存相關(guān)的數(shù)據(jù)户秤,當(dāng)接收到來(lái)自CPU的時(shí)間片中斷事件時(shí),就會(huì)按一定規(guī)則從這些線程中選擇一個(gè)逮矛,恢復(fù)它的運(yùn)行時(shí)數(shù)據(jù)鸡号,這樣CPU就可以繼續(xù)執(zhí)行這個(gè)線程了。
- 也就是其實(shí)就單核CUP而言橱鹏,并沒有辦法實(shí)現(xiàn)真正意義上的并發(fā)執(zhí)行膜蠢,只是CPU快速地在多條線程之間調(diào)度,CPU調(diào)度線程的時(shí)間足夠快莉兰,就造成了多線程并發(fā)執(zhí)行的假象。并且就單核CPU而言多線程可以解決線程阻塞的問題礁竞,但是其本身運(yùn)行效率并沒有提高糖荒,多CPU的并行運(yùn)算才真正解決了運(yùn)行效率問題。
- 系統(tǒng)中正在運(yùn)行的每一個(gè)應(yīng)用程序都是一個(gè)進(jìn)程模捂,每個(gè)進(jìn)程系統(tǒng)都會(huì)分配給它獨(dú)立的內(nèi)存運(yùn)行捶朵。也就是說,在iOS系統(tǒng)中中狂男,每一個(gè)應(yīng)用都是一個(gè)進(jìn)程综看。
- 一個(gè)進(jìn)程的所有任務(wù)都在線程中進(jìn)行,因此每個(gè)進(jìn)程至少要有一個(gè)線程岖食,也就是主線程红碑。那多線程其實(shí)就是一個(gè)進(jìn)程開啟多條線程,讓所有任務(wù)并發(fā)執(zhí)行泡垃。
- 多線程在一定意義上實(shí)現(xiàn)了進(jìn)程內(nèi)的資源共享析珊,以及效率的提升。同時(shí)蔑穴,在一定程度上相對(duì)獨(dú)立忠寻,它是程序執(zhí)行流的最小單元,是進(jìn)程中的一個(gè)實(shí)體存和,是執(zhí)行程序最基本的單元奕剃,有自己棧和寄存器。
- 上面這些你是不是都知道捐腿,但是我偏要說纵朋,哦呵呵。既然我們聊線程叙量,那我們就先從線程開刀倡蝙。
Pthreads && NSThread
先來(lái)看與線程有最直接關(guān)系的一套C的API:
Pthreads
POSIX線程(POSIX threads),簡(jiǎn)稱Pthreads绞佩,是線程的POSIX標(biāo)準(zhǔn)寺鸥。該標(biāo)準(zhǔn)定義了創(chuàng)建和操縱線程的一整套API猪钮。在類Unix操作系統(tǒng)(Unix、Linux胆建、Mac OS X等)中烤低,都使用Pthreads作為操作系統(tǒng)的線程。
高大上有木有笆载,跨平臺(tái)有木有扑馁,你沒用過有木有!下面我們來(lái)看一下這個(gè)看似牛逼但真的基本用不到的Pthreads
是怎么用的:
不如我們來(lái)用Pthreads
創(chuàng)建一個(gè)線程去執(zhí)行一個(gè)任務(wù):
記得引入頭文件`#import "pthread.h"`
-(void)pthreadsDoTask{
/*
pthread_t:線程指針
pthread_attr_t:線程屬性
pthread_mutex_t:互斥對(duì)象
pthread_mutexattr_t:互斥屬性對(duì)象
pthread_cond_t:條件變量
pthread_condattr_t:條件屬性對(duì)象
pthread_key_t:線程數(shù)據(jù)鍵
pthread_rwlock_t:讀寫鎖
//
pthread_create():創(chuàng)建一個(gè)線程
pthread_exit():終止當(dāng)前線程
pthread_cancel():中斷另外一個(gè)線程的運(yùn)行
pthread_join():阻塞當(dāng)前的線程凉驻,直到另外一個(gè)線程運(yùn)行結(jié)束
pthread_attr_init():初始化線程的屬性
pthread_attr_setdetachstate():設(shè)置脫離狀態(tài)的屬性(決定這個(gè)線程在終止時(shí)是否可以被結(jié)合)
pthread_attr_getdetachstate():獲取脫離狀態(tài)的屬性
pthread_attr_destroy():刪除線程的屬性
pthread_kill():向線程發(fā)送一個(gè)信號(hào)
pthread_equal(): 對(duì)兩個(gè)線程的線程標(biāo)識(shí)號(hào)進(jìn)行比較
pthread_detach(): 分離線程
pthread_self(): 查詢線程自身線程標(biāo)識(shí)號(hào)
//
*創(chuàng)建線程
int pthread_create(pthread_t _Nullable * _Nonnull __restrict, //指向新建線程標(biāo)識(shí)符的指針
const pthread_attr_t * _Nullable __restrict, //設(shè)置線程屬性腻要。默認(rèn)值NULL。
void * _Nullable (* _Nonnull)(void * _Nullable), //該線程運(yùn)行函數(shù)的地址
void * _Nullable __restrict); //運(yùn)行函數(shù)所需的參數(shù)
*返回值:
*若線程創(chuàng)建成功涝登,則返回0
*若線程創(chuàng)建失敗雄家,則返回出錯(cuò)編號(hào)
*/
//
pthread_t thread = NULL;
NSString *params = @"Hello World";
int result = pthread_create(&thread, NULL, threadTask, (__bridge void *)(params));
result == 0 ? NSLog(@"creat thread success") : NSLog(@"creat thread failure");
//設(shè)置子線程的狀態(tài)設(shè)置為detached,則該線程運(yùn)行結(jié)束后會(huì)自動(dòng)釋放所有資源
pthread_detach(thread);
}
void *threadTask(void *params) {
NSLog(@"%@ - %@", [NSThread currentThread], (__bridge NSString *)(params));
return NULL;
}
輸出結(jié)果:
ThreadDemo[1197:143578] creat thread success
ThreadDemo[1197:143649] <NSThread: 0x600000262e40>{number = 3, name = (null)} - Hello World
從打印結(jié)果來(lái)看,該任務(wù)是在新開辟的線程中執(zhí)行的胀滚,但是感覺用起來(lái)超不友好趟济,很多東西需要自己管理,單單是任務(wù)隊(duì)列以及線程生命周期的管理就夠你頭疼的咽笼,那你寫出的代碼還能是藝術(shù)么顷编!其實(shí)之所以拋棄這套API很少用,是因?yàn)槲覀冇懈玫倪x擇:NSThread
剑刑。
NSThread
哎呀媳纬,它面向?qū)ο螅偃タ纯刺O果提供的API叛甫,對(duì)比一下Pthreads
层宫,簡(jiǎn)單明了,人生仿佛又充滿了陽(yáng)光和希望其监,我們先來(lái)一看一下系統(tǒng)提供給我們的API自然就知道怎么用了萌腿,來(lái)來(lái)來(lái),我給你注釋一下岸犊唷:
@interface NSThread : NSObject
//當(dāng)前線程
@property (class, readonly, strong) NSThread *currentThread;
//使用類方法創(chuàng)建線程執(zhí)行任務(wù)
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
//判斷當(dāng)前是否為多線程
+ (BOOL)isMultiThreaded;
//指定線程的線程參數(shù)毁菱,例如設(shè)置當(dāng)前線程的斷言處理器。
@property (readonly, retain) NSMutableDictionary *threadDictionary;
//當(dāng)前線程暫停到某個(gè)時(shí)間
+ (void)sleepUntilDate:(NSDate *)date;
//當(dāng)前線程暫停一段時(shí)間
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//退出當(dāng)前線程
+ (void)exit;
//當(dāng)前線程優(yōu)先級(jí)
+ (double)threadPriority;
//設(shè)置當(dāng)前線程優(yōu)先級(jí)
+ (BOOL)setThreadPriority:(double)p;
//指定線程對(duì)象優(yōu)先級(jí) 0.0~1.0锌历,默認(rèn)值為0.5
@property double threadPriority NS_AVAILABLE(10_6, 4_0);
//服務(wù)質(zhì)量
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
//線程名稱
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
//棧區(qū)大小
@property NSUInteger stackSize NS_AVAILABLE(10_5, 2_0);
//是否為主線程
@property (class, readonly) BOOL isMainThread NS_AVAILABLE(10_5, 2_0);
//獲取主線程
@property (class, readonly, strong) NSThread *mainThread NS_AVAILABLE(10_5, 2_0);
//初始化
- (instancetype)init NS_AVAILABLE(10_5, 2_0) NS_DESIGNATED_INITIALIZER;
//實(shí)例方法初始化贮庞,需要再調(diào)用start方法
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);
- (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
//線程狀態(tài),正在執(zhí)行
@property (readonly, getter=isExecuting) BOOL executing NS_AVAILABLE(10_5, 2_0);
//線程狀態(tài)究西,正在完成
@property (readonly, getter=isFinished) BOOL finished NS_AVAILABLE(10_5, 2_0);
//線程狀態(tài)窗慎,已經(jīng)取消
@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);
//取消,僅僅改變線程狀態(tài),并不能像exist一樣真正的終止線程
- (void)cancel NS_AVAILABLE(10_5, 2_0);
//開始
- (void)start NS_AVAILABLE(10_5, 2_0);
//線程需要執(zhí)行的代碼遮斥,一般寫子類的時(shí)候會(huì)用到
- (void)main NS_AVAILABLE(10_5, 2_0);
@end
另外峦失,還有一個(gè)NSObject的分類,瞅一眼:
@interface NSObject (NSThreadPerformAdditions)
//隱式的創(chuàng)建并啟動(dòng)線程术吗,并在指定的線程(主線程或子線程)上執(zhí)行方法尉辑。
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
@end
上面的介紹您還滿意嗎?小的幫您下載一張圖片较屿,您瞧好:
-(void)creatBigImageView{
self.bigImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:_bigImageView];
UIButton *startButton = [UIButton buttonWithType:UIButtonTypeSystem];
startButton.frame = CGRectMake(0, 0, self.view.frame.size.width / 2, 50);
startButton.backgroundColor = [UIColor grayColor];
[startButton setTitle:@"開始加載" forState:UIControlStateNormal];
[startButton addTarget:self action:@selector(loadImage) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:startButton];
UIButton *jamButton = [UIButton buttonWithType:UIButtonTypeSystem];
jamButton.frame = CGRectMake(self.view.frame.size.width / 2, 0, self.view.frame.size.width / 2, 50);
jamButton.backgroundColor = [UIColor grayColor];
[jamButton setTitle:@"阻塞測(cè)試" forState:UIControlStateNormal];
[jamButton addTarget:self action:@selector(jamTest) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:jamButton];
}
-(void)jamTest{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"線程阻塞" message:@"" delegate:nil cancelButtonTitle:@"好" otherButtonTitles:nil, nil];
[alertView show];
}
-(void)loadImage{
NSURL *imageUrl = [NSURL URLWithString:@"http://img5.duitang.com/uploads/item/201206/06/20120606174422_LZSeE.thumb.700_0.jpeg"];
NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
[self updateImageData:imageData];
}
-(void)updateImageData:(NSData*)imageData{
UIImage *image = [UIImage imageWithData:imageData];
self.bigImageView.image = image;
}
運(yùn)行結(jié)果:
我們可以清楚的看到隧魄,主線程阻塞了,用戶不可以進(jìn)行其他操作隘蝎,你見過這樣的應(yīng)用嗎购啄?
所以我們這樣改一下:
-(void)creatBigImageView{
self.bigImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:_bigImageView];
UIButton *startButton = [UIButton buttonWithType:UIButtonTypeSystem];
startButton.frame = CGRectMake(0, 20, self.view.frame.size.width / 2, 50);
startButton.backgroundColor = [UIColor grayColor];
[startButton setTitle:@"開始加載" forState:UIControlStateNormal];
[startButton addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:startButton];
UIButton *jamButton = [UIButton buttonWithType:UIButtonTypeSystem];
jamButton.frame = CGRectMake(self.view.frame.size.width / 2, 20, self.view.frame.size.width / 2, 50);
jamButton.backgroundColor = [UIColor grayColor];
[jamButton setTitle:@"阻塞測(cè)試" forState:UIControlStateNormal];
[jamButton addTarget:self action:@selector(jamTest) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:jamButton];
}
-(void)jamTest{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"阻塞測(cè)試" message:@"" delegate:nil cancelButtonTitle:@"好" otherButtonTitles:nil, nil];
[alertView show];
}
-(void)loadImageWithMultiThread{
//方法1:使用對(duì)象方法
//NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
//??啟動(dòng)一個(gè)線程并非就一定立即執(zhí)行,而是處于就緒狀態(tài)嘱么,當(dāng)CUP調(diào)度時(shí)才真正執(zhí)行
//[thread start];
//方法2:使用類方法
[NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];
}
-(void)loadImage{
NSURL *imageUrl = [NSURL URLWithString:@"http://img5.duitang.com/uploads/item/201206/06/20120606174422_LZSeE.thumb.700_0.jpeg"];
NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
//必須在主線程更新UI闸溃,Object:代表調(diào)用方法的參數(shù),不過只能傳遞一個(gè)參數(shù)(如果有多個(gè)參數(shù)請(qǐng)使用對(duì)象進(jìn)行封裝),waitUntilDone:是否線程任務(wù)完成執(zhí)行
[self performSelectorOnMainThread:@selector(updateImageData:) withObject:imageData waitUntilDone:YES];
//[self updateImageData:imageData];
}
-(void)updateImageData:(NSData*)imageData{
UIImage *image = [UIImage imageWithData:imageData];
self.bigImageView.image = image;
}
運(yùn)行結(jié)果:
哎呀拱撵,用多線程果然能解決線程阻塞的問題,并且
NSThread
也比Pthreads
好用表蝙,仿佛你對(duì)精通熟練使用多線程又有了一絲絲曙光拴测。假如我有很多不同類型的任務(wù),每個(gè)任務(wù)之間還有聯(lián)系和依賴府蛇,你是不是又懵逼了集索,上面的你是不是覺得又白看了,其實(shí)開發(fā)中我覺得NSThread
用到最多的就是[NSThread currentThread];
了汇跨。(不要慌务荆,往下看... ...)
GCD
GCD,全名Grand Central Dispatch
穷遂,中文名郭草地
函匕,是基于C語(yǔ)言的一套多線程開發(fā)API,一聽名字就是個(gè)狠角色蚪黑,也是目前蘋果官方推薦的多線程開發(fā)方式盅惜。可以說是使用方便忌穿,又不失逼格抒寂。總體來(lái)說掠剑,他解決我提到的上面直接操作線程帶來(lái)的難題屈芜,它自動(dòng)幫你管理了線程的生命周期以及任務(wù)的執(zhí)行規(guī)則。下面我們會(huì)頻繁的說道一個(gè)詞,那就是任務(wù)
井佑,說白了属铁,任務(wù)
其實(shí)就是你要執(zhí)行的那段代碼
。
任務(wù)管理方式——隊(duì)列
上面說當(dāng)我們要管理多個(gè)任務(wù)時(shí)毅糟,線程開發(fā)給我們帶來(lái)了一定的技術(shù)難度红选,或者說不方便性,GCD給出了我們統(tǒng)一管理任務(wù)的方式姆另,那就是隊(duì)列喇肋。我們來(lái)看一下iOS
多線程操作中的隊(duì)列:(??不管是串行還是并行,隊(duì)列都是按照FIFO的原則依次觸發(fā)任務(wù))
兩個(gè)通用隊(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í)行了。(等待發(fā)生)
兩個(gè)特殊隊(duì)列:
- 主隊(duì)列:系統(tǒng)為我們創(chuàng)建好的一個(gè)串行隊(duì)列印荔,牛逼之處在于它管理必須在主線程中執(zhí)行的任務(wù)低葫,屬于有勞保的。
- 全局隊(duì)列:系統(tǒng)為我們創(chuàng)建好的一個(gè)并行隊(duì)列仍律,使用起來(lái)與我們自己創(chuàng)建的并行隊(duì)列無(wú)本質(zhì)差別嘿悬。
任務(wù)執(zhí)行方式
說完隊(duì)列,相應(yīng)的水泉,任務(wù)除了管理善涨,還得執(zhí)行,要不然有錢不花草则,掉了白搭钢拧,并且在GCD中并不能直接開辟線程執(zhí)行任務(wù),所以在任務(wù)加入隊(duì)列之后炕横,GCD給出了兩種執(zhí)行方式——同步執(zhí)行(sync)和異步執(zhí)行(async)源内。
- 同步執(zhí)行:在當(dāng)前線程執(zhí)行任務(wù),不會(huì)開辟新的線程看锉。必須等到Block函數(shù)執(zhí)行完畢后姿锭,dispatch函數(shù)才會(huì)返回。
- 異步執(zhí)行:可以在新的線程中執(zhí)行任務(wù)伯铣,但不一定會(huì)開辟新的線程呻此。dispatch函數(shù)會(huì)立即返回, 然后Block在后臺(tái)異步執(zhí)行。
上面的這些理論都是本人在無(wú)數(shù)被套路背后總結(jié)出來(lái)的血淋淋的經(jīng)驗(yàn)腔寡,與君共享焚鲜,但是這么寫我猜你一定還是不明白,往下看,說不定有驚喜呢忿磅。
任務(wù)隊(duì)列組合方式
相信這個(gè)標(biāo)題你看過無(wú)數(shù)次糯彬?是不是看完也不知道到底怎么用?這么巧葱她,我也是撩扒,請(qǐng)相信下面這些肯定有你不知道并且想要的,我們從兩個(gè)最直接的點(diǎn)切入:
1. 線程死鎖
這個(gè)你是不是也看過無(wú)數(shù)次吨些?哈哈哈搓谆!你是不是覺得我又要開始復(fù)制黏貼了?請(qǐng)往下看:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1========%@",[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2========%@",[NSThread currentThread]);
});
NSLog(@"3========%@",[NSThread currentThread]);
}
運(yùn)行結(jié)果:
打印結(jié)果:
ThreadDemo[5615:874679] 1========<NSThread: 0x608000072440>{number = 1, name = main}
真不是我套路你豪墅,我們還是得分析一下為什么會(huì)死鎖泉手,因?yàn)榭偟脼槟切]有飽受過套路的人心里留下一段美好的回憶,分享代碼偶器,我們是認(rèn)真的斩萌!
事情是這樣的:
我們先做一個(gè)定義:- (void)viewDidLoad{} ---> 任務(wù)A,GCD同步任務(wù) --->任務(wù)B屏轰。
總而言之呢颊郎,大概是這樣的,首先霎苗,任務(wù)A在主隊(duì)列袭艺,并且已經(jīng)開始執(zhí)行,在主線程打印出1===... ...
叨粘,然后這時(shí)任務(wù)B被加入到主隊(duì)列中,并且同步執(zhí)行瘤睹,這尼瑪事都大了升敲,系統(tǒng)說,同步執(zhí)行啊轰传,那我不開新的線程了驴党,任務(wù)B說我要等我里面的Block函數(shù)執(zhí)行完成,要不我就不返回获茬,但是主隊(duì)列說了港庄,玩蛋去,我是串行的恕曲,你得等A執(zhí)行完才能輪到你鹏氧,不能壞了規(guī)矩,同時(shí)佩谣,任務(wù)B作為任務(wù)A的內(nèi)部函數(shù)把还,必須等任務(wù)B執(zhí)行完函數(shù)返回才能執(zhí)行下一個(gè)任務(wù)。那就造成了,任務(wù)A等待任務(wù)B完成才能繼續(xù)執(zhí)行吊履,但作為串行隊(duì)列的主隊(duì)列又不能讓任務(wù)B在任務(wù)A未完成之前開始執(zhí)行安皱,所以任務(wù)A等著任務(wù)B完成,任務(wù)B等著任務(wù)A完成艇炎,等待酌伊,永久的等待。所以就死鎖了缀踪。簡(jiǎn)單不居砖?下面我們慎重看一下我們無(wú)意識(shí)書寫的代碼!
2. 這樣不死鎖
不如就寫個(gè)最簡(jiǎn)單的:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1========%@",[NSThread currentThread]);
NSLog(@"2========%@",[NSThread currentThread]);
NSLog(@"3========%@",[NSThread currentThread]);
}
打印結(jié)果:
ThreadDemo[5803:939324] 1========<NSThread: 0x600000078340>{number = 1, name = main}
ThreadDemo[5803:939324] 2========<NSThread: 0x600000078340>{number = 1, name = main}
ThreadDemo[5803:939324] 3========<NSThread: 0x600000078340>{number = 1, name = main}
之前有人問:順序打印辜贵,沒毛病悯蝉,全在主線程執(zhí)行,而且順序執(zhí)行托慨,那它們一定是在主隊(duì)列同步執(zhí)行的氨怯伞!那為什么沒有死鎖厚棵?蘋果的操作系統(tǒng)果然高深敖妒馈!
其實(shí)這里有一個(gè)誤區(qū)婆硬,那就是任務(wù)在主線程順序執(zhí)行就是主隊(duì)列狠轻。其實(shí)一點(diǎn)關(guān)系都沒有,如果當(dāng)前在主線程彬犯,同步執(zhí)行任務(wù)向楼,不管在什么隊(duì)列任務(wù)都是順序執(zhí)行。把所有任務(wù)都以異步執(zhí)行的方式加入到主隊(duì)列中谐区,你會(huì)發(fā)現(xiàn)它們也是順序執(zhí)行的湖蜕。
相信你知道上面的死鎖情況后,你一定會(huì)手賤改成這樣試試:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1========%@",[NSThread currentThread]);
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"2========%@",[NSThread currentThread]);
});
NSLog(@"3========%@",[NSThread currentThread]);
}
打印結(jié)果:
ThreadDemo[5830:947858] 1========<NSThread: 0x60000007bb80>{number = 1, name = main}
ThreadDemo[5830:947858] 2========<NSThread: 0x60000007bb80>{number = 1, name = main}
ThreadDemo[5830:947858] 3========<NSThread: 0x60000007bb80>{number = 1, name = main}
你發(fā)現(xiàn)正常執(zhí)行了宋列,并且是順序執(zhí)行的昭抒,你是不是若有所思,沒錯(cuò)炼杖,你想的和我想的是一樣的灭返,和上訴情況一樣,任務(wù)A在主隊(duì)列中坤邪,但是任務(wù)B加入到了全局隊(duì)列熙含,這時(shí)候,任務(wù)A和任務(wù)B沒有隊(duì)列的約束艇纺,所以任務(wù)B就先執(zhí)行嘍婆芦,執(zhí)行完畢之后函數(shù)返回怕磨,任務(wù)A接著執(zhí)行。
我猜你一定手賤這么改過:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1========%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2========%@",[NSThread currentThread]);
});
NSLog(@"3========%@",[NSThread currentThread]);
}
打印結(jié)果:
ThreadDemo[5911:962470] 1========<NSThread: 0x600000072700>{number = 1, name = main}
ThreadDemo[5911:962470] 3========<NSThread: 0x600000072700>{number = 1, name = main}
ThreadDemo[5911:962470] 2========<NSThread: 0x600000072700>{number = 1, name = main}
細(xì)心而帥氣的你一定發(fā)現(xiàn)不是順序打印了消约,而且也不會(huì)死鎖肠鲫,明明都是加到主隊(duì)列里了啊,其實(shí)當(dāng)任務(wù)A在執(zhí)行時(shí)或粮,任務(wù)B加入到了主隊(duì)列导饲,注意哦,是異步執(zhí)行氯材,所以dispatch函數(shù)不會(huì)等到Block執(zhí)行完成才返回渣锦,dispatch函數(shù)返回后,那任務(wù)A可以繼續(xù)執(zhí)行氢哮,Block任務(wù)我們可以認(rèn)為在下一幀順序加入隊(duì)列袋毙,并且默認(rèn)無(wú)限下一幀執(zhí)行。這就是為什么你看到2===... ...
是最后輸出的了冗尤。(??一個(gè)函數(shù)的有多個(gè)內(nèi)部函數(shù)異步執(zhí)行時(shí)听盖,不會(huì)造成死鎖的同時(shí),任務(wù)A執(zhí)行完畢后裂七,這些異步執(zhí)行的內(nèi)部函數(shù)會(huì)順序執(zhí)行)皆看。
我們說說隊(duì)列與執(zhí)行方式的搭配
上面說了系統(tǒng)自帶的兩個(gè)隊(duì)列,下面我們來(lái)用自己創(chuàng)建的隊(duì)列研究一下各種搭配情況背零。
我們先創(chuàng)建兩個(gè)隊(duì)列腰吟,并且測(cè)試方法都是在主線程中調(diào)用:
//串行隊(duì)列
self.serialQueue = dispatch_queue_create("serialQueue.ys.com", DISPATCH_QUEUE_SERIAL);
//并行隊(duì)列
self.concurrentQueue = dispatch_queue_create("concurrentQueue.ys.com", DISPATCH_QUEUE_CONCURRENT);
1. 串行隊(duì)列 + 同步執(zhí)行
-(void)queue_taskTest{
dispatch_sync(self.serialQueue, ^{
NSLog(@"1========%@",[NSThread currentThread]);
//[self nslogCount:10000 number:1];
});
dispatch_sync(self.serialQueue, ^{
NSLog(@"2========%@",[NSThread currentThread]);
//[self nslogCount:10000 number:2];
});
dispatch_sync(self.serialQueue, ^{
NSLog(@"3========%@",[NSThread currentThread]);
//[self nslogCount:10000 number:3];
});
NSLog(@"4========%@",[NSThread currentThread]);
}
打印結(jié)果:
ThreadDemo[6735:1064390] 1========<NSThread: 0x600000073cc0>{number = 1, name = main}
ThreadDemo[6735:1064390] 2========<NSThread: 0x600000073cc0>{number = 1, name = main}
ThreadDemo[6735:1064390] 3========<NSThread: 0x600000073cc0>{number = 1, name = main}
ThreadDemo[6735:1064390] 4========<NSThread: 0x600000073cc0>{number = 1, name = main}
全部都在當(dāng)前線程順序執(zhí)行,也就是說徙瓶,同步執(zhí)行不具備開辟新線程的能力毛雇。
2. 串行隊(duì)列 + 異步執(zhí)行
-(void)queue_taskTest{
dispatch_async(self.serialQueue, ^{
NSLog(@"1========%@",[NSThread currentThread]);
//[self nslogCount:10000 number:1];
});
dispatch_async(self.serialQueue, ^{
NSLog(@"2========%@",[NSThread currentThread]);
//[self nslogCount:10000 number:2];
});
dispatch_async(self.serialQueue, ^{
NSLog(@"3========%@",[NSThread currentThread]);
//[self nslogCount:10000 number:3];
});
NSLog(@"4========%@",[NSThread currentThread]);
}
打印結(jié)果:
ThreadDemo[6774:1073235] 4========<NSThread: 0x60800006e9c0>{number = 1, name = main}
ThreadDemo[6774:1073290] 1========<NSThread: 0x608000077000>{number = 3, name = (null)}
ThreadDemo[6774:1073290] 2========<NSThread: 0x608000077000>{number = 3, name = (null)}
ThreadDemo[6774:1073290] 3========<NSThread: 0x608000077000>{number = 3, name = (null)}
先打印了4,然后順序在子線程中打印1侦镇,2禾乘,3。說明異步執(zhí)行具有開辟新線程的能力虽缕,并且串行隊(duì)列必須等到前一個(gè)任務(wù)執(zhí)行完才能開始執(zhí)行下一個(gè)任務(wù),同時(shí)蒲稳,異步執(zhí)行會(huì)使內(nèi)部函數(shù)率先返回氮趋,不會(huì)與正在執(zhí)行的外部函數(shù)發(fā)生死鎖。
3. 并行隊(duì)列 + 同步執(zhí)行
-(void)queue_taskTest{
dispatch_sync(self.concurrentQueue, ^{
NSLog(@"1========%@",[NSThread currentThread]);
//[self nslogCount:10000 number:1];
});
dispatch_sync(self.concurrentQueue, ^{
NSLog(@"2========%@",[NSThread currentThread]);
//[self nslogCount:10000 number:2];
});
dispatch_sync(self.concurrentQueue, ^{
NSLog(@"3========%@",[NSThread currentThread]);
//[self nslogCount:10000 number:3];
});
NSLog(@"4========%@",[NSThread currentThread]);
}
運(yùn)行結(jié)果:
ThreadDemo[7012:1113594] 1========<NSThread: 0x60800007e340>{number = 1, name = main}
ThreadDemo[7012:1113594] 2========<NSThread: 0x60800007e340>{number = 1, name = main}
ThreadDemo[7012:1113594] 3========<NSThread: 0x60800007e340>{number = 1, name = main}
ThreadDemo[7012:1113594] 4========<NSThread: 0x60800007e340>{number = 1, name = main}
未開啟新的線程執(zhí)行任務(wù)江耀,并且Block函數(shù)執(zhí)行完成后dispatch函數(shù)才會(huì)返回剩胁,才能繼續(xù)向下執(zhí)行,所以我們看到的結(jié)果是順序打印的祥国。
4. 并行隊(duì)列 + 異步執(zhí)行
-(void)queue_taskTest{
dispatch_async(self.concurrentQueue, ^{
NSLog(@"1========%@",[NSThread currentThread]);
//[self nslogCount:10000 number:1];
});
dispatch_async(self.concurrentQueue, ^{
NSLog(@"2========%@",[NSThread currentThread]);
//[self nslogCount:10000 number:2];
});
dispatch_async(self.concurrentQueue, ^{
NSLog(@"3========%@",[NSThread currentThread]);
//[self nslogCount:10000 number:3];
});
NSLog(@"4========%@",[NSThread currentThread]);
}
打印結(jié)果:
ThreadDemo[7042:1117492] 1========<NSThread: 0x600000071900>{number = 3, name = (null)}
ThreadDemo[7042:1117491] 3========<NSThread: 0x608000070240>{number = 5, name = (null)}
ThreadDemo[7042:1117451] 4========<NSThread: 0x600000067400>{number = 1, name = main}
ThreadDemo[7042:1117494] 2========<NSThread: 0x600000071880>{number = 4, name = (null)}
開辟了多個(gè)線程昵观,觸發(fā)任務(wù)的時(shí)機(jī)是順序的晾腔,但是我們看到完成任務(wù)的時(shí)間卻是隨機(jī)的,這取決于CPU對(duì)于不同線程的調(diào)度分配啊犬,但是灼擂,線程不是無(wú)條件無(wú)限開辟的,當(dāng)任務(wù)量足夠大時(shí)觉至,線程是會(huì)重復(fù)利用的。
劃一下重點(diǎn)啊
1. 對(duì)于單核CPU來(lái)說,不存在真正意義上的并行非春,所以垫竞,多線程執(zhí)行任務(wù),其實(shí)也只是一個(gè)人在干活应闯,CPU的調(diào)度決定了非等待任務(wù)的執(zhí)行速率纤控,同時(shí)對(duì)于非等待任務(wù),多線程并沒有真正意義提高效率碉纺。
2. 線程可以簡(jiǎn)單的認(rèn)為就是一段代碼+運(yùn)行時(shí)數(shù)據(jù)船万。
3. 同步執(zhí)行會(huì)在當(dāng)前線程執(zhí)行任務(wù),不具備開辟線程的能力或者說沒有必要開辟新的線程惜辑。并且唬涧,同步執(zhí)行必須等到Block函數(shù)執(zhí)行完畢,dispatch函數(shù)才會(huì)返回盛撑,從而阻塞同一串行隊(duì)列中外部方法的執(zhí)行碎节。
4. 異步執(zhí)行dispatch函數(shù)會(huì)直接返回,Block函數(shù)我們可以認(rèn)為它會(huì)在下一幀加入隊(duì)列抵卫,并根據(jù)所在隊(duì)列目前的任務(wù)情況無(wú)限下一幀執(zhí)行狮荔,從而不會(huì)阻塞當(dāng)前外部任務(wù)的執(zhí)行。同時(shí)介粘,只有異步執(zhí)行才有開辟新線程的必要殖氏,但是異步執(zhí)行不一定會(huì)開辟新線程。
5. 只要是隊(duì)列姻采,肯定是FIFO(先進(jìn)先出)雅采,但是誰(shuí)先執(zhí)行完要看第1條。
6. 只要是串行隊(duì)列慨亲,肯定要等上一個(gè)任務(wù)執(zhí)行完成婚瓜,才能開始下一個(gè)任務(wù)。但是并行隊(duì)列當(dāng)上一個(gè)任務(wù)開始執(zhí)行后刑棵,下一個(gè)任務(wù)就可以開始執(zhí)行巴刻。
7. 想要開辟新線程必須讓任務(wù)在異步執(zhí)行,想要開辟多個(gè)線程蛉签,只有讓任務(wù)在并行隊(duì)列中異步執(zhí)行才可以胡陪。執(zhí)行方式和隊(duì)列類型多層組合在一定程度上能夠?qū)崿F(xiàn)對(duì)于代碼執(zhí)行順序的調(diào)度沥寥。
8. 同步+串行:未開辟新線程,串行執(zhí)行任務(wù)柠座;同步+并行:未開辟新線程邑雅,串行執(zhí)行任務(wù);異步+串行:新開辟一條線程愚隧,串行執(zhí)行任務(wù)蒂阱;異步+并行:開辟多條新線程,并行執(zhí)行任務(wù)狂塘;在主線程中同步使用主隊(duì)列執(zhí)行任務(wù)录煤,會(huì)造成死鎖。
8. 對(duì)于多核CPU來(lái)說荞胡,線程數(shù)量也不能無(wú)限開辟妈踊,線程的開辟同樣會(huì)消耗資源,過多線程同時(shí)處理任務(wù)并不是你想像中的人多力量大泪漂。
GCD其他函數(shù)用法
1. dispatch_after
該函數(shù)用于任務(wù)延時(shí)執(zhí)行廊营,其中參數(shù)dispatch_time_t
代表延時(shí)時(shí)長(zhǎng),dispatch_queue_t
代表使用哪個(gè)隊(duì)列萝勤。如果隊(duì)列未主隊(duì)列露筒,那么任務(wù)在主線程執(zhí)行,如果隊(duì)列為全局隊(duì)列或者自己創(chuàng)建的隊(duì)列敌卓,那么任務(wù)在子線程執(zhí)行慎式,代碼如下:
-(void)GCDDelay{
//主隊(duì)列延時(shí)
dispatch_time_t when_main = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
dispatch_after(when_main, dispatch_get_main_queue(), ^{
NSLog(@"main_%@",[NSThread currentThread]);
});
//全局隊(duì)列延時(shí)
dispatch_time_t when_global = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC));
dispatch_after(when_global, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"global_%@",[NSThread currentThread]);
});
//自定義隊(duì)列延時(shí)
dispatch_time_t when_custom = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
dispatch_after(when_custom, self.serialQueue, ^{
NSLog(@"custom_%@",[NSThread currentThread]);
});
}
打印結(jié)果:
ThreadDemo[1508:499647] main_<NSThread: 0x60000007cf40>{number = 1, name = main}
ThreadDemo[1508:499697] global_<NSThread: 0x608000262d80>{number = 3, name = (null)}
ThreadDemo[1508:499697] custom_<NSThread: 0x608000262d80>{number = 3, name = (null)}
2. dispatch_once
保證函數(shù)在整個(gè)生命周期內(nèi)只會(huì)執(zhí)行一次,看代碼趟径。
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"%@",[NSThread currentThread]);
});
}
打印結(jié)果:
ThreadDemo[1524:509261] <NSThread: 0x600000262940>{number = 1, name = main}
無(wú)論你怎么瘋狂的點(diǎn)擊瘪吏,在第一次打印之后,輸出臺(tái)便巋然不動(dòng)蜗巧。
3. dispatch_group_async & dispatch_group_notify
試想掌眠,現(xiàn)在牛逼的你要現(xiàn)在兩張小圖,并且你要等兩張圖都下載完成之后把他們拼起來(lái)幕屹,你要怎么做蓝丙?我根本就不會(huì)把兩張圖拼成一張圖啊,牛逼的我怎么可能有這種想法呢望拖?
其實(shí)方法有很多渺尘,比如你可以一張一張下載,再比如使用局部變量和Blcok實(shí)現(xiàn)計(jì)數(shù)靠娱,但是既然今天我們講到這,那我們就得入鄉(xiāng)隨俗掠兄,用GCD來(lái)實(shí)現(xiàn)像云,有一個(gè)神器的東西叫做隊(duì)列組锌雀,當(dāng)加入到隊(duì)列組中的所有任務(wù)執(zhí)行完成之后,會(huì)調(diào)用dispatch_group_notify
函數(shù)通知任務(wù)全部完成迅诬,代碼如下:
-(void)GCDGroup{
//
[self jointImageView];
//
dispatch_group_t group = dispatch_group_create();
__block UIImage *image_1 = nil;
__block UIImage *image_2 = nil;
//在group中添加一個(gè)任務(wù)
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
image_1 = [self imageWithPath:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502706256731&di=371f5fd17184944d7e2b594142cd7061&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201605%2F14%2F20160514165210_LRCji.jpeg"];
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
image_2 = [self imageWithPath:@"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=776127947,2002573948&fm=26&gp=0.jpg"];
});
//group中所有任務(wù)執(zhí)行完畢腋逆,通知該方法執(zhí)行
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
self.imageView_1.image = image_1;
self.imageView_2.image = image_2;
//
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0f);
[image_2 drawInRect:CGRectMake(0, 0, 100, 100)];
[image_1 drawInRect:CGRectMake(100, 0, 100, 100)];
UIImage *image_3 = UIGraphicsGetImageFromCurrentImageContext();
self.imageView_3.image = image_3;
UIGraphicsEndImageContext();
});
}
-(void)jointImageView{
self.imageView_1 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 50, 100, 100)];
[self.view addSubview:_imageView_1];
self.imageView_2 = [[UIImageView alloc] initWithFrame:CGRectMake(140, 50, 100, 100)];
[self.view addSubview:_imageView_2];
self.imageView_3 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 200, 200, 100)];
[self.view addSubview:_imageView_3];
self.imageView_1.layer.borderColor = self.imageView_2.layer.borderColor = self.imageView_3.layer.borderColor = [UIColor grayColor].CGColor;
self.imageView_1.layer.borderWidth = self.imageView_2.layer.borderWidth = self.imageView_3.layer.borderWidth = 1;
}
4. dispatch_barrier_async
柵欄函數(shù),這么看來(lái)它能擋住或者分隔什么東西侈贷,別瞎猜了惩歉,反正你又猜不對(duì),看這俏蛮,使用此方法創(chuàng)建的任務(wù)撑蚌,會(huì)查找當(dāng)前隊(duì)列中有沒有其他任務(wù)要執(zhí)行,如果有搏屑,則等待已有任務(wù)執(zhí)行完畢后再執(zhí)行争涌,同時(shí),在此任務(wù)之后進(jìn)入隊(duì)列的任務(wù)辣恋,需要等待此任務(wù)執(zhí)行完成后亮垫,才能執(zhí)行∥肮牵看代碼饮潦,老鐵。(?? 這里并發(fā)隊(duì)列必須是自己創(chuàng)建的携狭。如果選擇全局隊(duì)列继蜡,這個(gè)函數(shù)和dispatch_async將會(huì)沒有差別。)
-(void)GCDbarrier{
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)1");
});
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)2");
});
// dispatch_barrier_async(self.concurrentQueue, ^{
// NSLog(@"任務(wù)barrier");
// });
// NSLog(@"big");
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)3");
});
// NSLog(@"apple");
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)4");
});
}
運(yùn)行結(jié)果:
ThreadDemo[1816:673351] 任務(wù)3
ThreadDemo[1816:673353] 任務(wù)1
ThreadDemo[1816:673350] 任務(wù)2
ThreadDemo[1816:673370] 任務(wù)4
是不是如你所料暑中,牛逼大了壹瘟,下面我們打開第一句注釋:
-(void)GCDbarrier{
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)1");
});
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)2");
});
dispatch_barrier_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)barrier");
});
// NSLog(@"big");
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)3");
});
// NSLog(@"apple");
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)4");
});
}
打印結(jié)果:
ThreadDemo[1833:678739] 任務(wù)2
ThreadDemo[1833:678740] 任務(wù)1
ThreadDemo[1833:678740] 任務(wù)barrier
ThreadDemo[1833:678740] 任務(wù)3
ThreadDemo[1833:678739] 任務(wù)4
這個(gè)結(jié)果和我們上面的解釋完美契合,我們可以簡(jiǎn)單的控制函數(shù)執(zhí)行的順序了鳄逾,你離大牛又近了一步稻轨,如果現(xiàn)在的你不會(huì)懷疑還有dispatch_barrier_sync
這個(gè)函數(shù)的話,說明... ...嘿嘿嘿雕凹,我們看一下這個(gè)函數(shù)和上面我們用到的函數(shù)的區(qū)別殴俱,你一定想到了,再打開第二個(gè)和第三個(gè)注釋枚抵,如下:
-(void)GCDbarrier{
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)1");
});
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)2");
});
dispatch_barrier_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)barrier");
});
NSLog(@"big");
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)3");
});
NSLog(@"apple");
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)4");
});
}
運(yùn)行結(jié)果:
ThreadDemo[1853:692434] 任務(wù)1
ThreadDemo[1853:692421] 任務(wù)2
ThreadDemo[1853:692387] big
ThreadDemo[1853:692421] 任務(wù)barrier
ThreadDemo[1853:692387] apple
ThreadDemo[1853:692421] 任務(wù)3
ThreadDemo[1853:692434] 任務(wù)4
不要著急线欲,我們換一下函數(shù):
-(void)GCDbarrier{
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)1");
});
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)2");
});
dispatch_barrier_sync(self.concurrentQueue, ^{
NSLog(@"任務(wù)barrier");
});
NSLog(@"big");
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)3");
});
NSLog(@"apple");
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任務(wù)4");
});
}
打印結(jié)果:
ThreadDemo[1874:711841] 任務(wù)1
ThreadDemo[1874:711828] 任務(wù)2
ThreadDemo[1874:711793] 任務(wù)barrier
ThreadDemo[1874:711793] big
ThreadDemo[1874:711793] apple
ThreadDemo[1874:711828] 任務(wù)3
ThreadDemo[1874:711841] 任務(wù)4
老鐵,發(fā)現(xiàn)了嗎汽摹?這兩個(gè)函數(shù)對(duì)于隊(duì)列的柵欄作用是一樣的李丰,但是對(duì)于該函數(shù)相對(duì)于其他內(nèi)部函數(shù)遵循了最開始說到的同步和異步的規(guī)則。你是不是有點(diǎn)懵逼逼泣,如果你蒙蔽了趴泌,那么請(qǐng)?jiān)诿恳粋€(gè)輸出后面打印出當(dāng)前的線程舟舒,如果你還是懵逼,那么請(qǐng)你重新看嗜憔,有勞秃励,不謝!
5. dispatch_apply
該函數(shù)用于重復(fù)執(zhí)行某個(gè)任務(wù)吉捶,如果任務(wù)隊(duì)列是并行隊(duì)列夺鲜,重復(fù)執(zhí)行的任務(wù)會(huì)并發(fā)執(zhí)行,如果任務(wù)隊(duì)列為串行隊(duì)列呐舔,則任務(wù)會(huì)順序執(zhí)行币励,需要注意的是,該函數(shù)為同步函數(shù)滋早,要防止線程阻塞和死鎖哦榄审,老鐵。
串行隊(duì)列:
-(void)GCDApply{
//重復(fù)執(zhí)行
dispatch_apply(5, self.serialQueue, ^(size_t i) {
NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
});
}
運(yùn)行結(jié)果:
ThreadDemo[1446:158101] 第0次_<NSThread: 0x600000079ac0>{number = 1, name = main}
ThreadDemo[1446:158101] 第1次_<NSThread: 0x600000079ac0>{number = 1, name = main}
ThreadDemo[1446:158101] 第2次_<NSThread: 0x600000079ac0>{number = 1, name = main}
ThreadDemo[1446:158101] 第3次_<NSThread: 0x600000079ac0>{number = 1, name = main}
ThreadDemo[1446:158101] 第4次_<NSThread: 0x600000079ac0>{number = 1, name = main}
并行隊(duì)列:
-(void)GCDApply{
//重復(fù)執(zhí)行
dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
});
}
運(yùn)行結(jié)果:
ThreadDemo[1461:160567] 第2次_<NSThread: 0x608000076000>{number = 4, name = (null)}
ThreadDemo[1461:160534] 第0次_<NSThread: 0x60800006d8c0>{number = 1, name = main}
ThreadDemo[1461:160566] 第3次_<NSThread: 0x60000007d480>{number = 5, name = (null)}
ThreadDemo[1461:160569] 第1次_<NSThread: 0x60000007d440>{number = 3, name = (null)}
ThreadDemo[1461:160567] 第4次_<NSThread: 0x608000076000>{number = 4, name = (null)}
死鎖:
-(void)GCDApply{
//重復(fù)執(zhí)行
dispatch_apply(5, dispatch_get_main_queue(), ^(size_t i) {
NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
});
}
運(yùn)行結(jié)果:
6. dispatch_semaphore_create & dispatch_semaphore_signal & dispatch_semaphore_wait
看這幾個(gè)函數(shù)的時(shí)候你需要拋開隊(duì)列杆麸,丟掉同步異步搁进,不要把它們想到一起,混為一談昔头,信號(hào)量只是控制任務(wù)執(zhí)行的一個(gè)條件而已饼问,相對(duì)于上面通過隊(duì)列以及執(zhí)行方式來(lái)控制線程的開辟和任務(wù)的執(zhí)行,它更貼近對(duì)于任務(wù)直接的控制揭斧。類似于單個(gè)隊(duì)列的最大并發(fā)數(shù)的控制機(jī)制莱革,提高并行效率的同時(shí),也防止太多線程的開辟對(duì)CPU早層負(fù)面的效率負(fù)擔(dān)讹开。
dispatch_semaphore_create
創(chuàng)建信號(hào)量盅视,初始值不能小于0;
dispatch_semaphore_wait
等待降低信號(hào)量旦万,也就是信號(hào)量-1闹击;
dispatch_semaphore_signal
提高信號(hào)量,也就是信號(hào)量+1成艘;
dispatch_semaphore_wait
和dispatch_semaphore_signal
通常配對(duì)使用赏半。
看一下代碼吧,老鐵淆两。
-(void)GCDSemaphore{
//
//dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
//dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(self.concurrentQueue, ^{
NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
//dispatch_semaphore_signal(semaphore);
});
});
}
你能猜到運(yùn)行結(jié)果嗎断箫?沒錯(cuò),就是你想的這樣秋冰,開辟了5個(gè)線程執(zhí)行任務(wù)仲义。
ThreadDemo[1970:506692] 第0次_<NSThread: 0x600000070f00>{number = 3, name = (null)}
ThreadDemo[1970:506711] 第1次_<NSThread: 0x6000000711c0>{number = 4, name = (null)}
ThreadDemo[1970:506713] 第2次_<NSThread: 0x6000000713c0>{number = 5, name = (null)}
ThreadDemo[1970:506691] 第3次_<NSThread: 0x600000070f40>{number = 6, name = (null)}
ThreadDemo[1970:506694] 第4次_<NSThread: 0x600000070440>{number = 7, name = (null)}
下一步你一定猜到了,把注釋的代碼打開:
-(void)GCDSemaphore{
//
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(self.concurrentQueue, ^{
NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
});
}
運(yùn)行結(jié)果:
ThreadDemo[2020:513651] 第0次_<NSThread: 0x608000073900>{number = 3, name = (null)}
ThreadDemo[2020:513651] 第1次_<NSThread: 0x608000073900>{number = 3, name = (null)}
ThreadDemo[2020:513651] 第2次_<NSThread: 0x608000073900>{number = 3, name = (null)}
ThreadDemo[2020:513651] 第3次_<NSThread: 0x608000073900>{number = 3, name = (null)}
ThreadDemo[2020:513651] 第4次_<NSThread: 0x608000073900>{number = 3, name = (null)}
很明顯,我開始說的是對(duì)的埃撵,哈哈哈哈尸诽,信號(hào)量是控制任務(wù)執(zhí)行的重要條件,當(dāng)信號(hào)量為0時(shí)盯另,所有任務(wù)等待,信號(hào)量越大洲赵,允許可并行執(zhí)行的任務(wù)數(shù)量越多鸳惯。
GCD就先說到這,很多API沒有涉及到叠萍,有興趣的同學(xué)們可以自己去看看芝发,重要的是方法和習(xí)慣,而不是你看過多少苛谷。
NSOperation && NSOperationQueue
如果上面的郭草地
如果你學(xué)會(huì)了辅鲸,那么這兩個(gè)東西你也不一定能學(xué)得會(huì)!
NSOperation
以及NSOperationQueue
是蘋果對(duì)于GCD的封裝腹殿,其中呢独悴,NSOperation
其實(shí)就是我們上面所說的任務(wù),但是這個(gè)類不能直接使用锣尉,我們要用他的兩個(gè)子類刻炒,NSBlockOperation
和NSInvocationOperation
,而NSOperationQueue
呢自沧,其實(shí)就是類似于GCD中的隊(duì)列坟奥,用于管理你加入到其中的任務(wù)。
NSOperation
它提供了關(guān)于任務(wù)的執(zhí)行拇厢,取消爱谁,以及隨時(shí)獲取任務(wù)的狀態(tài),添加任務(wù)依賴以及優(yōu)先級(jí)等方法和屬性孝偎,相對(duì)于GCD提供的方法來(lái)說访敌,更直觀,更方便邪媳,并且提供了更多的控制接口捐顷。(很多時(shí)候,蘋果設(shè)計(jì)的架構(gòu)是很棒的雨效,不要只是在乎他實(shí)現(xiàn)了什么迅涮,可能你學(xué)到的東西會(huì)更多,一不小心又吹牛逼了徽龟,哦呵呵)叮姑,有幾個(gè)方法和屬性我們了解一下:
@interface NSOperation : NSObject {
@private
id _private;
int32_t _private1;
#if __LP64__
int32_t _private1b;
#endif
}
- (void)start;//啟動(dòng)任務(wù) 默認(rèn)在當(dāng)前線程執(zhí)行
- (void)main;//自定義NSOperation,寫一個(gè)子類,重寫這個(gè)方法传透,在這個(gè)方法里面添加需要執(zhí)行的操作耘沼。
@property (readonly, getter=isCancelled) BOOL cancelled;//是否已經(jīng)取消,只讀
- (void)cancel;//取消任務(wù)
@property (readonly, getter=isExecuting) BOOL executing;//正在執(zhí)行朱盐,只讀
@property (readonly, getter=isFinished) BOOL finished;//執(zhí)行結(jié)束群嗤,只讀
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);//是否并發(fā),只讀
@property (readonly, getter=isReady) BOOL ready;//準(zhǔn)備執(zhí)行
- (void)addDependency:(NSOperation *)op;//添加依賴
- (void)removeDependency:(NSOperation *)op;//移除依賴
@property (readonly, copy) NSArray<NSOperation *> *dependencies;//所有依賴關(guān)系兵琳,只讀
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};//系統(tǒng)提供的優(yōu)先級(jí)關(guān)系枚舉
@property NSOperationQueuePriority queuePriority;//執(zhí)行優(yōu)先級(jí)
@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);//任務(wù)執(zhí)行完成之后的回調(diào)
- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);//阻塞當(dāng)前線程狂秘,等到某個(gè)operation執(zhí)行完畢。
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);//已廢棄躯肌,用qualityOfService替代者春。
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);//服務(wù)質(zhì)量,一個(gè)高質(zhì)量的服務(wù)就意味著更多的資源得以提供來(lái)更快的完成操作清女。
@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);//任務(wù)名稱
@end
然而NSOperation
本身是個(gè)抽象類钱烟,不能直接使用,我們有三種方式賦予它新的生命嫡丙,就是下面這三個(gè)東西拴袭,您坐穩(wěn)看好。
NSOperation自定義子類
這是我要說的第一個(gè)任務(wù)類型曙博,我們可以自定義繼承于NSOperation
的子類稻扬,并重寫父類提供的方法,實(shí)現(xiàn)一波具有特殊意義的任務(wù)羊瘩。比如我們?nèi)ハ螺d一個(gè)圖片:
.h
#import <UIKit/UIKit.h>
@protocol YSImageDownLoadOperationDelegate <NSObject>
-(void)YSImageDownLoadFinished:(UIImage*)image;
@end
@interface YSImageDownLoadOperation : NSOperation
-(id)initOperationWithUrl:(NSURL*)imageUrl delegate:(id<YSImageDownLoadOperationDelegate>)delegate;
@end
.m
#import "YSImageDownLoadOperation.h"
@implementation YSImageDownLoadOperation{
NSURL *_imageUrl;
id _delegate;
}
-(id)initOperationWithUrl:(NSURL*)imageUrl delegate:(id<YSImageDownLoadOperationDelegate>)delegate{
if (self == [super init]) {
_imageUrl = imageUrl;
_delegate = delegate;
}
return self;
}
-(void)main{
@autoreleasepool {
UIImage *image = [self imageWithUrl:_imageUrl];
if (_delegate && [_delegate respondsToSelector:@selector(YSImageDownLoadFinished:)]) {
[_delegate YSImageDownLoadFinished:image];
}
}
}
-(UIImage*)imageWithUrl:(NSURL*)url{
NSData *imageData = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:imageData];
return image;
}
@end
然后調(diào)用:
-(void)YSDownLoadImageOperationRun{
YSImageDownLoadOperation *ysOper = [[YSImageDownLoadOperation alloc] initOperationWithUrl:[NSURL URLWithString:@"http://img5.duitang.com/uploads/item/201206/06/20120606174422_LZSeE.thumb.700_0.jpeg"] delegate:self];
[ysOper start];
}
-(void)YSImageDownLoadFinished:(UIImage *)image{
NSLog(@"%@",image);
}
運(yùn)行打印結(jié)果:
ThreadDemo[4141:1100329] <UIImage: 0x60800009f630>, {700, 1050}
哦呵呵泰佳,其實(shí)自定義的任務(wù)更具有指向性,它可以滿足你特定的需求尘吗,但是一般用的比較少逝她,不知道是因?yàn)槲姨诉€是真的有很多更加方便的方法和思路實(shí)現(xiàn)這樣的邏輯。
NSBlockOperation
第二個(gè)睬捶,就是系統(tǒng)提供的NSOperation
的子類NSBlockOperation
黔宛,我們看一下他提供的API:
@interface NSBlockOperation : NSOperation {
@private
id _private2;
void *_reserved2;
}
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
- (void)addExecutionBlock:(void (^)(void))block;
@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;
@end
很簡(jiǎn)單,就這幾個(gè)擒贸,我們就用它實(shí)現(xiàn)一個(gè)任務(wù):
-(void)NSBlockOperationRun{
NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"NSBlockOperationRun_%@_%@",[NSOperationQueue currentQueue],[NSThread currentThread]);
}];
[blockOper start];
}
運(yùn)行結(jié)果:
ThreadDemo[4313:1121900] NSBlockOperationRun_<NSOperationQueue: 0x608000037420>{name = 'NSOperationQueue Main Queue'}_<NSThread: 0x60000006dd80>{number = 1, name = main}
我們發(fā)現(xiàn)這個(gè)任務(wù)是在當(dāng)前線程順序執(zhí)行的臀晃,我們發(fā)現(xiàn)還有一個(gè)方法addExecutionBlock:
試一下:
-(void)NSBlockOperationRun{
NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"NSBlockOperationRun_1_%@",[NSThread currentThread]);
}];
[blockOper addExecutionBlock:^{
NSLog(@"NSBlockOperationRun_2_%@",[NSThread currentThread]);
}];
[blockOper addExecutionBlock:^{
NSLog(@"NSBlockOperationRun_3_%@",[NSThread currentThread]);
}];
[blockOper addExecutionBlock:^{
NSLog(@"NSBlockOperationRun_4_%@",[NSThread currentThread]);
}];
[blockOper start];
}
打印結(jié)果:
ThreadDemo[4516:1169835] NSBlockOperationRun_1_<NSThread: 0x60000006d880>{number = 1, name = main}
ThreadDemo[4516:1169875] NSBlockOperationRun_3_<NSThread: 0x600000070800>{number = 4, name = (null)}
ThreadDemo[4516:1169877] NSBlockOperationRun_4_<NSThread: 0x6080000762c0>{number = 5, name = (null)}
ThreadDemo[4516:1169893] NSBlockOperationRun_2_<NSThread: 0x608000076100>{number = 3, name = (null)}
從打印結(jié)果來(lái)看,這個(gè)4個(gè)任務(wù)是異步并發(fā)執(zhí)行的介劫,開辟了多條線程徽惋。
NSInvocationOperation
第三個(gè),就是它了座韵,同樣也是系統(tǒng)提供給我們的一個(gè)任務(wù)類险绘,基于一個(gè)target對(duì)象以及一個(gè)selector來(lái)創(chuàng)建任務(wù)踢京,具體代碼:
-(void)NSInvocationOperationRun{
NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperSel) object:nil];
[invocationOper start];
}
-(void)invocationOperSel{
NSLog(@"NSInvocationOperationRun_%@",[NSThread currentThread]);
}
運(yùn)行結(jié)果:
ThreadDemo[4538:1173118] NSInvocationOperationRun_<NSThread: 0x60800006e900>{number = 1, name = main}
運(yùn)行結(jié)果與NSBlockOperation
單個(gè)block函數(shù)的執(zhí)行方式相同,同步順序執(zhí)行宦棺。的確系統(tǒng)的封裝給予我們關(guān)于任務(wù)更直觀的東西瓣距,但是對(duì)于多個(gè)任務(wù)的控制機(jī)制并不完善,所以我們有請(qǐng)下一位代咸,也許你會(huì)眼前一亮蹈丸。
NSOperationQueue
上面說道我們創(chuàng)建的NSOperation
任務(wù)對(duì)象可以通過start
方法來(lái)執(zhí)行,同樣我們可以把這個(gè)任務(wù)對(duì)象添加到一個(gè)NSOperationQueue對(duì)象中去執(zhí)行呐芥,好想有好東西白华,先看一下系統(tǒng)的API:
@interface NSOperationQueue : NSObject {
@private
id _private;
void *_reserved;
}
- (void)addOperation:(NSOperation *)op;//添加任務(wù)
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);//添加一組任務(wù)
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);//添加一個(gè)block形式的任務(wù)
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;//隊(duì)列中所有的任務(wù)數(shù)組
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);//隊(duì)列中的任務(wù)數(shù)
@property NSInteger maxConcurrentOperationCount;//最大并發(fā)數(shù)
@property (getter=isSuspended) BOOL suspended;//暫停
@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);//名稱
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);//服務(wù)質(zhì)量,一個(gè)高質(zhì)量的服務(wù)就意味著更多的資源得以提供來(lái)更快的完成操作贩耐。
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);
- (void)cancelAllOperations;//取消隊(duì)列中的所有任務(wù)
- (void)waitUntilAllOperationsAreFinished;//阻塞當(dāng)前線程,等到隊(duì)列中的任務(wù)全部執(zhí)行完畢厦取。
#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue NS_AVAILABLE(10_6, 4_0);//獲取當(dāng)前隊(duì)列
@property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0);//獲取主隊(duì)列
#endif
@end
來(lái)一段代碼開心開心:
-(void)NSOperationQueueRun{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperSel) object:nil];
[queue addOperation:invocationOper];
NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"NSBlockOperationRun_%@",[NSThread currentThread]);
}];
[queue addOperation:blockOper];
[queue addOperationWithBlock:^{
NSLog(@"QUEUEBlockOperationRun_%@",[NSThread currentThread]);
}];
}
打印結(jié)果:
ThreadDemo[4761:1205689] NSBlockOperationRun_<NSThread: 0x600000264480>{number = 4, name = (null)}
ThreadDemo[4761:1205691] NSInvocationOperationRun_<NSThread: 0x600000264380>{number = 3, name = (null)}
ThreadDemo[4761:1205706] QUEUEBlockOperationRun_<NSThread: 0x6000002645c0>{number = 5, name = (null)}
我們發(fā)現(xiàn)潮太,加入隊(duì)列之后不用調(diào)用任務(wù)的start
方法,隊(duì)列會(huì)幫你管理任務(wù)的執(zhí)行情況虾攻。上訴執(zhí)行結(jié)果說明這些任務(wù)在隊(duì)列中為并發(fā)執(zhí)行的铡买。
下面我們改變一下任務(wù)的優(yōu)先級(jí):
invocationOper.queuePriority = NSOperationQueuePriorityVeryLow;
運(yùn)行結(jié)果:
ThreadDemo[4894:1218440] QUEUEBlockOperationRun_<NSThread: 0x608000268880>{number = 3, name = (null)}
ThreadDemo[4894:1218442] NSBlockOperationRun_<NSThread: 0x60000026d340>{number = 4, name = (null)}
ThreadDemo[4894:1218457] NSInvocationOperationRun_<NSThread: 0x60000026d400>{number = 5, name = (null)}
我們發(fā)現(xiàn)優(yōu)先級(jí)低的任務(wù)會(huì)后執(zhí)行,但是霎箍,這并不是絕對(duì)的奇钞,還有很多東西可以左右CPU分配,以及操作系統(tǒng)對(duì)于任務(wù)和線程的控制漂坏,只能說景埃,優(yōu)先級(jí)會(huì)在一定程度上讓優(yōu)先級(jí)高的任務(wù)開始執(zhí)行。同時(shí)顶别,優(yōu)先級(jí)只對(duì)同一隊(duì)列中的任務(wù)有效哦谷徙。下面我們就看一個(gè)會(huì)忽視優(yōu)先級(jí)的情況。
添加依賴關(guān)系
-(void)NSOperationQueueRun{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *blockOper_1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 1000; i++) {
NSLog(@"blockOper_1_%@_%@",@(i),[NSThread currentThread]);
}
}];
NSBlockOperation *blockOper_2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 1000; i++) {
NSLog(@"blockOper_2_%@_%@",@(i),[NSThread currentThread]);
}
}];
[blockOper_1 addDependency:blockOper_2];
[queue addOperation:blockOper_1];
[queue addOperation:blockOper_2];
}
打印結(jié)果:
ThreadDemo[5066:1233824] blockOper_2_0_<NSThread: 0x600000078340>{number = 3, name = (null)}
ThreadDemo[5066:1233824] blockOper_2_1_<NSThread: 0x600000078340>{number = 3, name = (null)}
ThreadDemo[5066:1233824] blockOper_2_2_<NSThread: 0x600000078340>{number = 3, name = (null)}
ThreadDemo[5066:1233824] blockOper_2_3_<NSThread: 0x600000078340>{number = 3, name = (null)}
... ...
ThreadDemo[5066:1233824] blockOper_2_999_<NSThread: 0x600000078340>{number = 3, name = (null)}
ThreadDemo[5066:1233822] blockOper_1_0_<NSThread: 0x60000006ae80>{number = 4, name = (null)}
... ...
ThreadDemo[5066:1233822] blockOper_1_997_<NSThread: 0x60000006ae80>{number = 4, name = (null)}
ThreadDemo[5066:1233822] blockOper_1_998_<NSThread: 0x60000006ae80>{number = 4, name = (null)}
ThreadDemo[5066:1233822] blockOper_1_999_<NSThread: 0x60000006ae80>{number = 4, name = (null)}
通過打印結(jié)果我們可以看到驯绎,添加依賴之后完慧,依賴任務(wù)必須等待被依賴任務(wù)執(zhí)行完畢之后才會(huì)開始執(zhí)行。??剩失,就算依賴任務(wù)的優(yōu)先級(jí)再高屈尼,也是被依賴任務(wù)先執(zhí)行,同時(shí)拴孤,和優(yōu)先級(jí)不同脾歧,依賴關(guān)系不受隊(duì)列的局限,愛哪哪演熟,只要是我依賴于你涨椒,那你必須先執(zhí)行完,我才執(zhí)行。
隊(duì)列的最大并發(fā)數(shù)
就是說蚕冬,這個(gè)隊(duì)列最多可以有多少任務(wù)同時(shí)執(zhí)行免猾,或者說最多開辟多少條線程,如果設(shè)置為1囤热,那就一次只能執(zhí)行一個(gè)任務(wù)猎提,但是,不要以為這和GCD的串行隊(duì)列一樣旁蔼,就算最大并發(fā)數(shù)為1锨苏,隊(duì)列任務(wù)的執(zhí)行順序依然取決于很多因素。
關(guān)于NSOperationQueue
還有取消啊棺聊,暫停啊等操作方式伞租,大家可以試一下,應(yīng)該注意的是限佩,和學(xué)習(xí)GCD的方式不同葵诈,不要總是站在面向過程的角度看帶這些面向?qū)ο蟮念悾驗(yàn)樗拿嫦鄬?duì)象化的封裝過程中祟同,肯定有很多你看不到的面相過程的操作作喘,所以你也沒有必要用使用GCD的思想來(lái)套用它,否則你可能會(huì)迷糊的一塌糊涂晕城。
線程鎖
上面算是把多線程操作的方法講完了泞坦,下面說一下線程鎖機(jī)制。多線程操作是多個(gè)線程并行的砖顷,所以同一塊資源可能在同一時(shí)間被多個(gè)線程訪問贰锁,舉爛的例子就是買火車票,在就剩一個(gè)座時(shí)滤蝠,如果100個(gè)線程同時(shí)進(jìn)入李根,那么可能上火車時(shí)就有人得干仗了。為了維護(hù)世界和平几睛,人民安定房轿,所以我們講一下這個(gè)線程鎖。我們先實(shí)現(xiàn)一段代碼:
- (void)viewDidLoad {
[super viewDidLoad];
self.sourceArray_m = [NSMutableArray new];
[_sourceArray_m addObjectsFromArray:@[@"1",@"2",@"3",@"4",@"5",@"6"]];
[self threadLock];
}
-(void)threadLock{
for (int i = 0; i < 8; i++) {
dispatch_async(self.concurrentQueue, ^{
NSLog(@"%@",[self sourceOut]) ;
});
}
}
-(NSString*)sourceOut{
NSString *source = @"沒有了,取光了";
if (_sourceArray_m.count > 0) {
source = [_sourceArray_m lastObject];
[_sourceArray_m removeLastObject];
}
return source;
}
運(yùn)行打印結(jié)果:
ThreadDemo[5540:1291666] 6
ThreadDemo[5540:1291669] 6
ThreadDemo[5540:1291682] 5
ThreadDemo[5540:1291667] 4
ThreadDemo[5540:1291683] 3
ThreadDemo[5540:1291666] 2
ThreadDemo[5540:1291669] 1
ThreadDemo[5540:1291682] 沒有了,取光了
我們發(fā)現(xiàn)6
被取出來(lái)兩次(因?yàn)榇a簡(jiǎn)單所森,執(zhí)行效率較快囱持,所以這種情況不實(shí)必現(xiàn),耐心多試幾次)焕济,這樣的話就尷尬了纷妆,一張票賣了2次,這么惡劣的行為是不可能容忍的晴弃,所以我們需要正義的衛(wèi)士——線程鎖掩幢,我們就講最直接的兩種(之前說的GCD的很多方法同樣可以等價(jià)于線程鎖解決這些問題):
NSLock
代碼這樣寫:
- (void)viewDidLoad {
[super viewDidLoad];
self.lock = [[NSLock alloc] init];
self.sourceArray_m = [NSMutableArray new];
[_sourceArray_m addObjectsFromArray:@[@"1",@"2",@"3",@"4",@"5",@"6"]];
[self threadLock];
}
-(void)threadLock{
for (int i = 0; i < 8; i++) {
dispatch_async(self.concurrentQueue, ^{
NSLog(@"%@",[self sourceOut]) ;
});
}
}
-(NSString*)sourceOut{
NSString *source = @"沒有了,取光了";
[_lock lock];
if (_sourceArray_m.count > 0) {
source = [_sourceArray_m lastObject];
[_sourceArray_m removeLastObject];
}
[_lock unlock];
return source;
}
運(yùn)行結(jié)果:
ThreadDemo[5593:1298144] 5
ThreadDemo[5593:1298127] 6
ThreadDemo[5593:1298126] 4
ThreadDemo[5593:1298129] 3
ThreadDemo[5593:1298146] 2
ThreadDemo[5593:1298144] 1
ThreadDemo[5593:1298127] 沒有了,取光了
ThreadDemo[5593:1298147] 沒有了,取光了
這樣就保證了被Lock的資源只能同時(shí)讓一個(gè)線程進(jìn)行訪問逊拍,從而也就保證了線程安全。
@synchronized
這個(gè)也很簡(jiǎn)單际邻,有時(shí)候也會(huì)用到這個(gè)芯丧,要傳入一個(gè)同步對(duì)象(一般就是self),然后將你需要加鎖的資源放入代碼塊中世曾,如果該資源有線程正在訪問時(shí)缨恒,會(huì)讓其他線程等待,直接上代碼:
- (void)viewDidLoad {
[super viewDidLoad];
self.sourceArray_m = [NSMutableArray new];
[_sourceArray_m addObjectsFromArray:@[@"1",@"2",@"3",@"4",@"5",@"6"]];
[self threadLock];
}
-(void)threadLock{
for (int i = 0; i < 8; i++) {
dispatch_async(self.concurrentQueue, ^{
NSLog(@"%@",[self sourceOut]) ;
});
}
}
-(NSString*)sourceOut{
NSString *source = @"沒有了,取光了";
@synchronized (self) {
if (_sourceArray_m.count > 0) {
source = [_sourceArray_m lastObject];
[_sourceArray_m removeLastObject];
}
}
return source;
}
運(yùn)行結(jié)果:
ThreadDemo[5625:1301834] 5
ThreadDemo[5625:1301835] 6
ThreadDemo[5625:1301837] 4
ThreadDemo[5625:1301852] 3
ThreadDemo[5625:1301834] 1
ThreadDemo[5625:1301854] 2
ThreadDemo[5625:1301835] 沒有了,取光了
ThreadDemo[5625:1301855] 沒有了,取光了
結(jié)語(yǔ)
看來(lái)該結(jié)束了B痔F丁!就到這吧血巍,小弟已經(jīng)盡力了萧锉,帶大家入個(gè)門,這條路小弟只能陪你走到這了述寡。