關(guān)于iOS多線程,我說佛寿,你聽幌墓,沒準(zhǔn)你就懂了!

事出必有因冀泻,今天我想和你聊聊線程的原因就是——當(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_waitdispatch_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è)子類刻炒,NSBlockOperationNSInvocationOperation,而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è)門,這條路小弟只能陪你走到這了述寡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末柿隙,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子辨赐,更是在濱河造成了極大的恐慌,老刑警劉巖京办,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掀序,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡惭婿,警方通過查閱死者的電腦和手機(jī)不恭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)财饥,“玉大人换吧,你說我怎么就攤上這事≡啃牵” “怎么了沾瓦?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)谦炒。 經(jīng)常有香客問我贯莺,道長(zhǎng),這世上最難降的妖魔是什么宁改? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任缕探,我火速辦了婚禮,結(jié)果婚禮上还蹲,老公的妹妹穿的比我還像新娘爹耗。我一直安慰自己耙考,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布潭兽。 她就那樣靜靜地躺著倦始,像睡著了一般。 火紅的嫁衣襯著肌膚如雪讼溺。 梳的紋絲不亂的頭發(fā)上楣号,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音怒坯,去河邊找鬼炫狱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛剔猿,可吹牛的內(nèi)容都是我干的视译。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼归敬,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼酷含!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起汪茧,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤椅亚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后舱污,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呀舔,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年扩灯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了媚赖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡珠插,死狀恐怖惧磺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捻撑,我是刑警寧澤磨隘,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站顾患,受9級(jí)特大地震影響琳拭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜描验,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一白嘁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧膘流,春花似錦絮缅、人聲如沸鲁沥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)画恰。三九已至,卻和暖如春吸奴,著一層夾襖步出監(jiān)牢的瞬間允扇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工则奥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留考润,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓读处,卻偏偏與公主長(zhǎng)得像糊治,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子罚舱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • iOS多線程編程 基本知識(shí) 1. 進(jìn)程(process) 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序井辜,就是一段程序的執(zhí)...
    陵無(wú)山閱讀 6,043評(píng)論 1 14
  • 前言: 最近想回顧一下多線程問題,看到一篇文章寫的非常詳細(xì),為了便于以后查找以及加深印象,就照著原文摘錄了下文,原...
    FM_0138閱讀 956評(píng)論 1 1
  • 93650345d0d1閱讀 128評(píng)論 0 0
  • 今天葬禮7點(diǎn)半開始,9點(diǎn)結(jié)束管闷,電鎬挖坑粥脚,自備吊車作業(yè),鏟車鏟土包个,一切機(jī)械化 機(jī)械代替不了的是親情刷允,我今天瞌了一百多...
    京心達(dá)張新波閱讀 67評(píng)論 0 1
  • 你認(rèn)識(shí)朱頂紅嗎? 朱頂紅/花語(yǔ):渴望被愛赃蛛,追求愛恃锉。另外還有表示自己纖弱渴望被關(guān)愛的意思搀菩。 相信養(yǎng)過花的人都認(rèn)識(shí)呕臂,朱...
    卉藍(lán)潔閱讀 366評(píng)論 0 2