IOS 開發(fā)多線程

前言

前言:了解多線程之前首先了解一些計(jì)算機(jī)的基本概念
計(jì)算機(jī)操作系統(tǒng)的基本概念
進(jìn)程: 一個(gè)具有一定獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合的一次運(yùn)行活動(dòng)扣泊〗叮可以理解成一個(gè)運(yùn)行中的應(yīng)用程序。
線程: 程序執(zhí)行流的最小單元旷赖,線程是進(jìn)程中的一個(gè)實(shí)體顺又。
同步: 只能在當(dāng)前線程按先后順序依次執(zhí)行更卒,不開啟新線程等孵。
異步: 可以在當(dāng)前線程開啟多個(gè)新線程執(zhí)行,可不按順序執(zhí)行蹂空。
隊(duì)列: 裝載線程任務(wù)的隊(duì)形結(jié)構(gòu)俯萌。
并發(fā): 線程執(zhí)行可以同時(shí)一起進(jìn)行執(zhí)行。
串行: 線程執(zhí)行只能依次逐一先后有序的執(zhí)行上枕。

1.什么是多線程

多線程是一個(gè)比較輕量級(jí)的方法來實(shí)現(xiàn)單個(gè)應(yīng)用程序內(nèi)多個(gè)代碼執(zhí)行路徑咐熙。在系 統(tǒng)級(jí)別內(nèi),程序并排執(zhí)行辨萍,系統(tǒng)分配到每個(gè)程序的執(zhí)行時(shí)間是基于該程序的所需時(shí)間 和其他程序的所需時(shí)間來決定的棋恼。然而在每個(gè)應(yīng)程序的內(nèi)部,存在一個(gè)或多個(gè)執(zhí)行線 程锈玉,它同時(shí)或在一個(gè)幾乎同時(shí)發(fā)生的方式里執(zhí)行不同的任務(wù)爪飘。系統(tǒng)本身管理這些執(zhí)行 的線程,調(diào)度它們?cè)诳捎玫膬?nèi)核上運(yùn)行拉背,并在需要讓其他線程執(zhí)行的時(shí)候搶先打斷它 們师崎。
從技術(shù)角度來看,一個(gè)線程就是一個(gè)需要管理執(zhí)行代碼的內(nèi)核級(jí)和應(yīng)用級(jí)數(shù)據(jù)結(jié) 構(gòu)組合椅棺。內(nèi)核級(jí)結(jié)構(gòu)協(xié)助調(diào)度線程事件犁罩,并搶占式調(diào)度一個(gè)線程到可用的內(nèi)核之上。 應(yīng)用級(jí)結(jié)構(gòu)包括用于存儲(chǔ)函數(shù)調(diào)用的調(diào)用堆棧和應(yīng)用程序需要管理和操作線程屬性 和狀態(tài)的結(jié)構(gòu)两疚。
摘取自<多線程編程指南>

IOS多線程概念圖.png
2.iOS多線程對(duì)比

1.NSThread每個(gè)NSThread對(duì)象對(duì)應(yīng)一個(gè)線程床估,真正最原始的線程。
1)優(yōu)點(diǎn):NSThread 輕量級(jí)最低诱渤,相對(duì)簡單顷窒。
2)缺點(diǎn):手動(dòng)管理所有的線程活動(dòng),如生命周期、線程同步鞋吉、睡眠等鸦做。

2.1.1NSThread三種實(shí)現(xiàn)開啟線程方式
1.動(dòng)態(tài)實(shí)例化
NSThread *thread = [[NSThread alloc] initWithTarget:self            selector:@selector(loadImageSource:) object:imgUrl];
thread.threadPriority = 1;// 設(shè)置線程的優(yōu)先級(jí)(0.0 - 1.0,1.0最高級(jí))
[thread start];
2.靜態(tài)實(shí)例化
[NSThread detachNewThreadSelector:@selector(loadImageSource:) toTarget:self withObject:imgUrl];
3.隱式實(shí)例化
[self performSelectorInBackground:@selector(loadImageSource:) withObject:imgUrl];
//在指定線程上操作
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];
2.1.2使用場(chǎng)景1(開啟子線程加載一張圖片)

解決線程阻塞問題在資源下載過程中谓着,由于網(wǎng)絡(luò)原因有時(shí)候很難保證下載時(shí)間泼诱,如果不使用多線程可能用戶完成一個(gè)下載操作需要長時(shí)間的等待,這個(gè)過程中無法進(jìn)行其他操作赊锚。下面演示一個(gè)采用多線程下載圖片的過程治筒,在這個(gè)示例中點(diǎn)擊按鈕會(huì)啟動(dòng)一個(gè)線程去下載圖片,下載完成后使用UIImageView將圖片顯示到界面中舷蒲∷释啵可以看到用戶點(diǎn)擊完下載按鈕后,不管圖片是否下載完成都可以繼續(xù)操作界面牲平,不會(huì)造成阻塞堤框。


@interface NSThreadViewController (){
    UIImageView *_imageView;
}

@end

@implementation NSThreadViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor whiteColor];
    [self layoutUI];
}

#pragma mark 界面布局
-(void)layoutUI{
    _imageView =[[UIImageView alloc]initWithFrame:[UIScreen mainScreen].bounds];
    _imageView.contentMode=UIViewContentModeScaleAspectFit;
    [self.view addSubview:_imageView];
    
    UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
    button.frame=CGRectMake(50, 500, 220, 25);
    [button setTitle:@"加載圖片" forState:UIControlStateNormal];
    //添加方法
    [button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}


#pragma mark 將圖片顯示到界面
-(void)updateImage:(NSData *)imageData{
    UIImage *image=[UIImage imageWithData:imageData];
    _imageView.image=image;
}

#pragma mark 請(qǐng)求圖片數(shù)據(jù)
-(NSData *)requestData{
    //對(duì)于多線程操作建議把線程操作放到@autoreleasepool中
    @autoreleasepool {
        NSURL *url=[NSURL URLWithString:@"https://store.storeimages.cdn-apple.com/8750/as-images.apple.com/is/image/AppleInc/aos/published/images/s/eg/segment/hero/segment-hero-macbook-2017_GEO_CN?wid=400&hei=300&fmt=png-alpha&qlt=95&.v=1501280548817"];
        NSData *data=[NSData dataWithContentsOfURL:url];
        return data;
    }
}

#pragma mark 加載圖片
-(void)loadImage{
    //請(qǐng)求數(shù)據(jù)
    NSData *data= [self requestData];
    /*將數(shù)據(jù)顯示到UI控件,注意只能在主線程中更新UI,
     另外performSelectorOnMainThread方法是NSObject的分類方法,每個(gè)NSObject對(duì)象都有此方法纵柿,
     它調(diào)用的selector方法是當(dāng)前調(diào)用控件的方法蜈抓,例如使用UIImageView調(diào)用的時(shí)候selector就是UIImageView的方法
     Object:代表調(diào)用方法的參數(shù),不過只能傳遞一個(gè)參數(shù)(如果有多個(gè)參數(shù)請(qǐng)使用對(duì)象進(jìn)行封裝)
     waitUntilDone:是否線程任務(wù)完成執(zhí)行
     */
    [self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES];
}

#pragma mark 多線程下載圖片
-(void)loadImageWithMultiThread{
    //方法1:使用對(duì)象方法
    //創(chuàng)建一個(gè)線程,第一個(gè)參數(shù)是請(qǐng)求的操作昂儒,第二個(gè)參數(shù)是操作方法的參數(shù)
    //    NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
    //    //啟動(dòng)一個(gè)線程沟使,注意啟動(dòng)一個(gè)線程并非就一定立即執(zhí)行,而是處于就緒狀態(tài)渊跋,當(dāng)系統(tǒng)調(diào)度時(shí)才真正執(zhí)行
    //    [thread start];
    
    //方法2:使用類方法
    [NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];
}
picture.gif
2.1.3使用場(chǎng)景2(開啟子線程加載多張圖片)

#import "NSThreadTwoViewController.h"
#import "KCImageData.h"
#define ROW_COUNT 5
#define COLUMN_COUNT 3
#define ROW_HEIGHT 100
#define ROW_WIDTH ROW_HEIGHT
#define CELL_SPACING 10
@interface NSThreadTwoViewController (){
    NSMutableArray *_imageViews;
}


@end

@implementation NSThreadTwoViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor whiteColor];

    [self layoutUI];
}

#pragma mark 界面布局
-(void)layoutUI{
    //創(chuàng)建多個(gè)圖片控件用于顯示圖片
    _imageViews=[NSMutableArray array];
    for (int r=0; r<ROW_COUNT; r++) {
        for (int c=0; c<COLUMN_COUNT; c++) {
            UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), 100+r*ROW_HEIGHT+(r*CELL_SPACING                           ), ROW_WIDTH, ROW_HEIGHT)];
            imageView.contentMode=UIViewContentModeScaleAspectFit;
            //            imageView.backgroundColor=[UIColor redColor];
            [self.view addSubview:imageView];
            [_imageViews addObject:imageView];
            
        }
    }
    
    UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
    button.frame=CGRectMake(50, 500, 220, 25);
    [button setTitle:@"加載圖片" forState:UIControlStateNormal];
    //添加方法
    [button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

#pragma mark 將圖片顯示到界面
-(void)updateImage:(KCImageData *)imageData{
    UIImage *image=[UIImage imageWithData:imageData.data];
    UIImageView *imageView= _imageViews[imageData.index];
    imageView.image=image;
}

#pragma mark 請(qǐng)求圖片數(shù)據(jù)
-(NSData *)requestData:(NSInteger )index{
    //對(duì)于多線程操作建議把線程操作放到@autoreleasepool中
    @autoreleasepool {
        NSURL *url=[NSURL URLWithString:@"https://store.storeimages.cdn-apple.com/8750/as-images.apple.com/is/image/AppleInc/aos/published/images/s/eg/segment/hero/segment-hero-macbook-2017_GEO_CN?wid=400&hei=300&fmt=png-alpha&qlt=95&.v=1501280548817"];
        NSData *data=[NSData dataWithContentsOfURL:url];
        return data;
    }
}

#pragma mark 加載圖片
-(void)loadImage:(NSNumber *)index{
    //    NSLog(@"%i",i);
    //currentThread方法可以取得當(dāng)前操作線程
    NSLog(@"current thread:%@",[NSThread currentThread]);
    
    NSInteger i=[index integerValue];
    
    //    NSLog(@"%i",i);//未必按順序輸出
    
    NSData *data= [self requestData:i];
    
    KCImageData *imageData=[[KCImageData alloc]init];
    imageData.index= i;
    imageData.data=data;
    [self performSelectorOnMainThread:@selector(updateImage:) withObject:imageData waitUntilDone:YES];
}

#pragma mark 多線程下載圖片
-(void)loadImageWithMultiThread{
    //創(chuàng)建多個(gè)線程用于填充圖片
    for (int i=0; i<ROW_COUNT*COLUMN_COUNT; ++i) {
        //        [NSThread detachNewThreadSelector:@selector(loadImage:) toTarget:self withObject:[NSNumber numberWithInt:i]];
        NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]];
        thread.name=[NSString stringWithFormat:@"myThread%i",i];//設(shè)置線程名稱
        [thread start];
    }
}

效果圖.gif

從上面的運(yùn)行效果大家不難發(fā)現(xiàn)腊嗡,圖片并未按順序加載,原因有兩個(gè):第一拾酝,每個(gè)線程的實(shí)際執(zhí)行順序并不一定按順序執(zhí)行(雖然是按順序啟動(dòng))燕少;第二,每個(gè)線程執(zhí)行時(shí)實(shí)際網(wǎng)絡(luò)狀況很可能不一致微宝。當(dāng)然網(wǎng)絡(luò)問題無法改變棺亭,只能盡可能讓網(wǎng)速更快,但是可以改變線程的優(yōu)先級(jí)蟋软,讓15個(gè)線程優(yōu)先執(zhí)行某個(gè)線程镶摘。線程優(yōu)先級(jí)范圍為0~1,值越大優(yōu)先級(jí)越高岳守,每個(gè)線程的優(yōu)先級(jí)默認(rèn)為0.5凄敢。修改圖片下載方法如下,改變最后一張圖片加載的優(yōu)先級(jí)湿痢,這樣可以提高它被優(yōu)先加載的幾率涝缝,但是它也未必就第一個(gè)加載扑庞。因?yàn)槭紫绕渌€程是先啟動(dòng)的,其次網(wǎng)絡(luò)狀況我們沒辦法修改拒逮。

線程狀態(tài)

線程狀態(tài)分為isExecuting(正在執(zhí)行)罐氨、isFinished(已經(jīng)完成)、isCancellled(已經(jīng)取消)三種滩援。其中取消狀態(tài)程序可以干預(yù)設(shè)置栅隐,只要調(diào)用線程的cancel方法即可。但是需要注意在主線程中僅僅能設(shè)置線程狀態(tài)玩徊,并不能真正停止當(dāng)前線程租悄,如果要終止線程必須在線程中調(diào)用exist方法,這是一個(gè)靜態(tài)方法恩袱,調(diào)用該方法可以退出當(dāng)前線程泣棋。
假設(shè)在圖片加載過程中點(diǎn)擊停止按鈕讓沒有完成的線程停止加載,具體實(shí)現(xiàn)如下:

#import "NSthreadThreeViewController.h"
#import "KCImageData.h"
#define ROW_COUNT 5
#define COLUMN_COUNT 3
#define ROW_HEIGHT 100
#define ROW_WIDTH ROW_HEIGHT
#define CELL_SPACING 10
#define IMAGE_COUNT 9

@interface NSthreadThreeViewController (){
    NSMutableArray *_imageViews;
    NSMutableArray *_imageNames;
    NSMutableArray *_threads;
}

@end

@implementation NSthreadThreeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor whiteColor];

    [self layoutUI];
}
#pragma mark 界面布局
-(void)layoutUI{
    //創(chuàng)建多個(gè)圖片空間用于顯示圖片
    _imageViews=[NSMutableArray array];
    for (int r=0; r<ROW_COUNT; r++) {
        for (int c=0; c<COLUMN_COUNT; c++) {
            UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), 100+r*ROW_HEIGHT+(r*CELL_SPACING                           ), ROW_WIDTH, ROW_HEIGHT)];
            imageView.contentMode=UIViewContentModeScaleAspectFit;
            //            imageView.backgroundColor=[UIColor redColor];
            [self.view addSubview:imageView];
            [_imageViews addObject:imageView];
            
        }
    }
    
    //加載按鈕
    UIButton *buttonStart=[UIButton buttonWithType:UIButtonTypeRoundedRect];
    buttonStart.frame=CGRectMake(50, 500, 100, 25);
    [buttonStart setTitle:@"加載圖片" forState:UIControlStateNormal];
    [buttonStart addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:buttonStart];
    
    //停止按鈕
    UIButton *buttonStop=[UIButton buttonWithType:UIButtonTypeRoundedRect];
    buttonStop.frame=CGRectMake(160, 500, 100, 25);
    [buttonStop setTitle:@"停止加載" forState:UIControlStateNormal];
    [buttonStop addTarget:self action:@selector(stopLoadImage) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:buttonStop];
    
    //創(chuàng)建圖片鏈接
    _imageNames=[NSMutableArray array];
     for (int i=0; i<ROW_COUNT*COLUMN_COUNT; i++) {
        [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
    }
     }
     
#pragma mark 將圖片顯示到界面
     -(void)updateImage:(KCImageData *)imageData{
         UIImage *image=[UIImage imageWithData:imageData.data];
         UIImageView *imageView= _imageViews[imageData.index];
         imageView.image=image;
     }
     
#pragma mark 請(qǐng)求圖片數(shù)據(jù)
     -(NSData *)requestData:(NSInteger )index{
         //對(duì)于多線程操作建議把線程操作放到@autoreleasepool中
         @autoreleasepool {
             NSURL *url=[NSURL URLWithString:_imageNames[index]];
             NSData *data=[NSData dataWithContentsOfURL:url];
             
             return data;
         }
     }
     
#pragma mark 加載圖片
     -(void)loadImage:(NSNumber *)index{
         NSInteger i=[index integerValue];
         
         NSData *data= [self requestData:i];
         
         
         NSThread *currentThread=[NSThread currentThread];
         
         //    如果當(dāng)前線程處于取消狀態(tài)畔塔,則退出當(dāng)前線程
         if (currentThread.isCancelled) {
             NSLog(@"thread(%@) will be cancelled!",currentThread);
             [NSThread exit];//取消當(dāng)前線程
         }
         
         KCImageData *imageData=[[KCImageData alloc]init];
         imageData.index=i;
         imageData.data=data;
         [self performSelectorOnMainThread:@selector(updateImage:) withObject:imageData waitUntilDone:YES];
     }
     
#pragma mark 多線程下載圖片
     -(void)loadImageWithMultiThread{
         int count=ROW_COUNT*COLUMN_COUNT;
         _threads=[NSMutableArray arrayWithCapacity:count];
         
         //創(chuàng)建多個(gè)線程用于填充圖片
         for (int i=0; i<count; ++i) {
             NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]];
             thread.name=[NSString stringWithFormat:@"myThread%i",i];//設(shè)置線程名稱
             [_threads addObject:thread];
         }
         //循環(huán)啟動(dòng)線程
         for (int i=0; i<count; ++i) {
             NSThread *thread= _threads[i];
             [thread start];
         }
     }
     
#pragma mark 停止加載圖片
     -(void)stopLoadImage{
         for (int i=0; i<ROW_COUNT*COLUMN_COUNT; i++) {
             NSThread *thread= _threads[i];
             //判斷線程是否完成潭辈,如果沒有完成則設(shè)置為取消狀態(tài)
             //注意設(shè)置為取消狀態(tài)僅僅是改變了線程狀態(tài)而言,并不能終止線程
             if (!thread.isFinished) {
                 [thread cancel];
                 
             }
         }
     }

實(shí)現(xiàn)效果如圖

效果圖.gif

2.NSOperation
自帶線程管理的抽象類俩檬。
1)優(yōu)點(diǎn):自帶線程周期管理萎胰,操作上可更注重自己邏輯碾盟。
2)缺點(diǎn):面向?qū)ο蟮某橄箢惻锪桑荒軐?shí)現(xiàn)它或者使用它定義好的兩個(gè)子類:NSInvocationOperation 和 NSBlockOperation。

主要的實(shí)現(xiàn)方式:結(jié)合NSOperation和NSOperationQueue實(shí)現(xiàn)多線程編程冰肴。
實(shí)例化NSOperation的子類屈藐,綁定執(zhí)行的操作。
創(chuàng)建NSOperationQueue隊(duì)列熙尉,將NSOperation實(shí)例添加進(jìn)來联逻。
系統(tǒng)會(huì)自動(dòng)將NSOperationQueue隊(duì)列中檢測(cè)取出和執(zhí)行NSOperation的操作。

①.NSInvocationOperation創(chuàng)建線程检痰。
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl];
//[invocationOperation start];//直接會(huì)在當(dāng)前線程主線程執(zhí)行
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:invocationOperation];
②.NSBlockOperation創(chuàng)建線程
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
    [self loadImageSource:imgUrl];
}];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:blockOperation];

2.2 NSInvocationOperation使用場(chǎng)景

首先使用NSInvocationOperation進(jìn)行一張圖片的加載演示包归,整個(gè)過程就是:創(chuàng)建一個(gè)操作,在這個(gè)操作中指定調(diào)用方法和參數(shù)铅歼,然后加入到操作隊(duì)列公壤。其他代碼基本不用修改,直接修加載圖片方法如下:

-(void)loadImageWithMultiThread{
    /*創(chuàng)建一個(gè)調(diào)用操作
     object:調(diào)用方法參數(shù)
    */
    NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
    //創(chuàng)建完NSInvocationOperation對(duì)象并不會(huì)調(diào)用椎椰,它由一個(gè)start方法啟動(dòng)操作厦幅,但是注意如果直接調(diào)用start方法,則此操作會(huì)在主線程中調(diào)用慨飘,一般不會(huì)這么操作,而是添加到NSOperationQueue中
//    [invocationOperation start];
    
    //創(chuàng)建操作隊(duì)列
    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    //注意添加到操作隊(duì)后确憨,隊(duì)列會(huì)開啟一個(gè)線程執(zhí)行此操作
    [operationQueue addOperation:invocationOperation];
}
2.2.2 NSBlockOperation使用場(chǎng)景:下面采用NSBlockOperation創(chuàng)建多個(gè)線程加載圖片。

#import "NSOperationViewController.h"
#import "KCImageData.h"
#define ROW_COUNT 5
#define COLUMN_COUNT 3
#define ROW_HEIGHT 100
#define ROW_WIDTH ROW_HEIGHT
#define CELL_SPACING 10

@interface NSOperationViewController (){
    NSMutableArray *_imageViews;
    NSMutableArray *_imageNames;
}

@end

@implementation NSOperationViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    [self layoutUI];
}
#pragma mark 界面布局
-(void)layoutUI{
    //創(chuàng)建多個(gè)圖片控件用于顯示圖片
    _imageViews=[NSMutableArray array];
    for (int r=0; r<ROW_COUNT; r++) {
        for (int c=0; c<COLUMN_COUNT; c++) {
            UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), 100+r*ROW_HEIGHT+(r*CELL_SPACING                           ), ROW_WIDTH, ROW_HEIGHT)];
            imageView.contentMode=UIViewContentModeScaleAspectFit;
            //            imageView.backgroundColor=[UIColor redColor];
            [self.view addSubview:imageView];
            [_imageViews addObject:imageView];
            
        }
    }
    
    UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
    button.frame=CGRectMake(50, 500, 220, 25);
    [button setTitle:@"加載圖片" forState:UIControlStateNormal];
    //添加方法
    [button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    
    //創(chuàng)建圖片鏈接
    _imageNames=[NSMutableArray array];
    for (int i=0; i<ROW_COUNT*COLUMN_COUNT; i++) {
        [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
    }
}

#pragma mark 將圖片顯示到界面
-(void)updateImageWithData:(NSData *)data andIndex:(NSInteger)index{
    UIImage *image=[UIImage imageWithData:data];
    UIImageView *imageView= _imageViews[index];
    imageView.image=image;
}

#pragma mark 請(qǐng)求圖片數(shù)據(jù)
-(NSData *)requestData:(NSInteger )index{
    //對(duì)于多線程操作建議把線程操作放到@autoreleasepool中
    @autoreleasepool {
        NSURL *url=[NSURL URLWithString:_imageNames[index]];
        NSData *data=[NSData dataWithContentsOfURL:url];
        
        return data;
    }
}

#pragma mark 加載圖片
-(void)loadImage:(NSNumber *)index{
    NSInteger i=[index integerValue];
    
    //請(qǐng)求數(shù)據(jù)
    NSData *data= [self requestData:i];
    NSLog(@"%@",[NSThread currentThread]);
    //更新UI界面,此處調(diào)用了主線程隊(duì)列的方法(mainQueue是UI主線程)
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [self updateImageWithData:data andIndex:i];
    }];
}



#pragma mark 多線程下載圖片
//-(void)loadImageWithMultiThread{
//    int count=ROW_COUNT*COLUMN_COUNT;
//    //創(chuàng)建操作隊(duì)列
//    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
//    operationQueue.maxConcurrentOperationCount=5;//設(shè)置最大并發(fā)線程數(shù)
//    //創(chuàng)建多個(gè)線程用于填充圖片
//    for (int i=0; i<count; ++i) {
//        //方法1:創(chuàng)建操作塊添加到隊(duì)列
//        //        //創(chuàng)建多線程操作
//        //        NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
//        //            [self loadImage:[NSNumber numberWithInt:i]];
//        //        }];
//        //        //創(chuàng)建操作隊(duì)列
//        //
//        //        [operationQueue addOperation:blockOperation];
//        
//        //方法2:直接使用操隊(duì)列添加操作
//        [operationQueue addOperationWithBlock:^{
//            [self loadImage:[NSNumber numberWithInt:i]];
//        }];
//        
//    }
//}

-(void)loadImageWithMultiThread{//設(shè)置優(yōu)先加載最后一張照片
    int count=ROW_COUNT*COLUMN_COUNT;
    //創(chuàng)建操作隊(duì)列
    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    operationQueue.maxConcurrentOperationCount=5;//設(shè)置最大并發(fā)線程數(shù)
    
    NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
        [self loadImage:[NSNumber numberWithInt:(count-1)]];
    }];
    //創(chuàng)建多個(gè)線程用于填充圖片
    for (int i=0; i<count-1; ++i) {
        //方法1:創(chuàng)建操作塊添加到隊(duì)列
        //創(chuàng)建多線程操作
        NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
            [self loadImage:[NSNumber numberWithInt:i]];
        }];
        //設(shè)置依賴操作為最后一張圖片加載操作
        [blockOperation addDependency:lastBlockOperation];
        
        [operationQueue addOperation:blockOperation];
        
    }
    //將最后一個(gè)圖片的加載操作加入線程隊(duì)列
    [operationQueue addOperation:lastBlockOperation];
}

效果圖.gif
線程執(zhí)行順序

前面使用NSThread很難控制線程的執(zhí)行順序,但是使用NSOperation就容易多了休弃,每個(gè)NSOperation可以設(shè)置依賴線程吞歼。假設(shè)操作A依賴于操作B,線程操作隊(duì)列在啟動(dòng)線程時(shí)就會(huì)首先執(zhí)行B操作塔猾,然后執(zhí)行A浆熔。對(duì)于前面優(yōu)先加載最后一張圖的需求,只要設(shè)置前面的線程操作的依賴線程為最后一個(gè)操作即可桥帆。修改圖片加載方法如下:

-(void)loadImageWithMultiThread{
    int count=ROW_COUNT*COLUMN_COUNT;
    //創(chuàng)建操作隊(duì)列
    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    operationQueue.maxConcurrentOperationCount=5;//設(shè)置最大并發(fā)線程數(shù)
    
    NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
        [self loadImage:[NSNumber numberWithInt:(count-1)]];
    }];
    //創(chuàng)建多個(gè)線程用于填充圖片
    for (int i=0; i<count-1; ++i) {
        //方法1:創(chuàng)建操作塊添加到隊(duì)列
        //創(chuàng)建多線程操作
        NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
            [self loadImage:[NSNumber numberWithInt:i]];
        }];
        //設(shè)置依賴操作為最后一張圖片加載操作
        [blockOperation addDependency:lastBlockOperation];
        
        [operationQueue addOperation:blockOperation];
        
    }
    //將最后一個(gè)圖片的加載操作加入線程隊(duì)列
    [operationQueue addOperation:lastBlockOperation];
}

3.GCD
Grand Central Dispatch (GCD)是Apple開發(fā)的一個(gè)多核編程的解決方法医增。
1)優(yōu)點(diǎn):最高效,避開并發(fā)陷阱老虫。
2)缺點(diǎn):基于C實(shí)現(xiàn)叶骨。

通過 GCD,開發(fā)者不用再直接跟線程打交道了祈匙,只需要向隊(duì)列中添加代碼塊即可忽刽,GCD 在后端管理著一個(gè)線程池。GCD 不僅決定著你的代碼塊將在哪個(gè)線程被執(zhí)行夺欲,它還根據(jù)可用的系統(tǒng)資源對(duì)這些線程進(jìn)行管理跪帝。這樣可以將開發(fā)者從線程管理的工作中解放出來,通過集中的管理線程些阅,來緩解大量線程被創(chuàng)建的問題伞剑。

GCD 帶來的另一個(gè)重要改變是,作為開發(fā)者可以將工作考慮為一個(gè)隊(duì)列市埋,而不是一堆線程黎泣,這種并行的抽象模型更容易掌握和使用。

GCD 公開有 5 個(gè)不同的隊(duì)列:運(yùn)行在主線程中的 main queue缤谎,3 個(gè)不同優(yōu)先級(jí)的后臺(tái)隊(duì)列抒倚,以及一個(gè)優(yōu)先級(jí)更低的后臺(tái)隊(duì)列(用于 I/O)。 另外坷澡,開發(fā)者可以創(chuàng)建自定義隊(duì)列:串行或者并行隊(duì)列托呕。自定義隊(duì)列非常強(qiáng)大,在自定義隊(duì)列中被調(diào)度的所有 block 最終都將被放入到系統(tǒng)的全局隊(duì)列中和線程池中频敛。

GCD.png

Serial Diapatch Queue 串行隊(duì)列

當(dāng)任務(wù)相互依賴项郊,具有明顯的先后順序的時(shí)候,使用串行隊(duì)列是一個(gè)不錯(cuò)的選擇 創(chuàng)建一個(gè)串行隊(duì)列:

dispatch_queue_t serialDispatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_SERIAL); 

第一個(gè)參數(shù)為隊(duì)列名姻政,第二個(gè)參數(shù)為隊(duì)列類型呆抑,當(dāng)然,第二個(gè)參數(shù)如果寫NULL汁展,創(chuàng)建出來的也是一個(gè)串行隊(duì)列鹊碍。然后我們?cè)诋惒骄€程來執(zhí)行這個(gè)隊(duì)列:

dispatch_async(serialDispatchQueue, ^{  
    NSLog(@"1");  
});  
    
dispatch_async(serialDispatchQueue, ^{  
    sleep(2);  
    NSLog(@"2");  
});  
    
dispatch_async(serialDispatchQueue, ^{  
    sleep(1);  
    NSLog(@"3");  
}); 

為了能更好的理解厌殉,我給每個(gè)異步線程都添加了一個(gè)log,看一下日志平臺(tái)的log:

2016-03-07 10:17:13.907 GCD[2195:61497] 1
2016-03-07 10:17:15.911 GCD[2195:61497] 2
2016-03-07 10:17:16.912 GCD[2195:61497] 3

串行隊(duì)列使用場(chǎng)景
使用串行隊(duì)列時(shí)首先要?jiǎng)?chuàng)建一個(gè)串行隊(duì)列侈咕,然后調(diào)用異步調(diào)用方法公罕,在此方法中傳入串行隊(duì)列和線程操作即可自動(dòng)執(zhí)行。下面使用線程隊(duì)列演示圖片的加載過程耀销,你會(huì)發(fā)現(xiàn)多張圖片會(huì)按順序加載楼眷,因?yàn)楫?dāng)前隊(duì)列中只有一個(gè)線程。

#import "GCDViewController.h"
#import "KCImageData.h"
#define ROW_COUNT 5
#define COLUMN_COUNT 3
#define ROW_HEIGHT 100
#define ROW_WIDTH ROW_HEIGHT
#define CELL_SPACING 10
@interface GCDViewController (){
    NSMutableArray *_imageViews;
    NSMutableArray *_imageNames;
}


@end

@implementation GCDViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    [self layoutUI];
    
   
}
#pragma mark 界面布局
-(void)layoutUI{
    //創(chuàng)建多個(gè)圖片控件用于顯示圖片
    _imageViews=[NSMutableArray array];
    for (int r=0; r<ROW_COUNT; r++) {
        for (int c=0; c<COLUMN_COUNT; c++) {
            UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), 100+r*ROW_HEIGHT+(r*CELL_SPACING                           ), ROW_WIDTH, ROW_HEIGHT)];
            imageView.contentMode=UIViewContentModeScaleAspectFit;
            //            imageView.backgroundColor=[UIColor redColor];
            [self.view addSubview:imageView];
            [_imageViews addObject:imageView];
            
        }
    }
    
    UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
    button.frame=CGRectMake(50, 500, 220, 25);
    [button setTitle:@"加載圖片" forState:UIControlStateNormal];
    //添加方法
    [button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    
    //創(chuàng)建圖片鏈接
    _imageNames=[NSMutableArray array];
    for (int i=0; i<ROW_COUNT*COLUMN_COUNT; i++) {
        [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
    }
    
}

#pragma mark 將圖片顯示到界面
-(void)updateImageWithData:(NSData *)data andIndex:(NSInteger )index{
    UIImage *image=[UIImage imageWithData:data];
    UIImageView *imageView= _imageViews[index];
    imageView.image=image;
}

#pragma mark 請(qǐng)求圖片數(shù)據(jù)
-(NSData *)requestData:(NSInteger )index{
    NSURL *url=[NSURL URLWithString:_imageNames[index]];
    NSData *data=[NSData dataWithContentsOfURL:url];
    
    return data;
}

#pragma mark 加載圖片
-(void)loadImage:(NSNumber *)index{
    
    //如果在串行隊(duì)列中會(huì)發(fā)現(xiàn)當(dāng)前線程打印變化完全一樣熊尉,因?yàn)樗麄冊(cè)谝粋€(gè)線程中
    NSLog(@"thread is :%@",[NSThread currentThread]);
    
    NSInteger i=[index integerValue];
    //請(qǐng)求數(shù)據(jù)
    NSData *data= [self requestData:i];
    //更新UI界面,此處調(diào)用了GCD主線程隊(duì)列的方法
    dispatch_queue_t mainQueue= dispatch_get_main_queue();
    
    dispatch_async(mainQueue, ^{
        [self updateImageWithData:data andIndex:i];
    });
}

#pragma mark 多線程下載圖片
-(void)loadImageWithMultiThread{
    int count=ROW_COUNT*COLUMN_COUNT;
    
    /*創(chuàng)建一個(gè)串行隊(duì)列
     第一個(gè)參數(shù):隊(duì)列名稱
     第二個(gè)參數(shù):隊(duì)列類型
     */
    dispatch_queue_t serialQueue=dispatch_queue_create("myThreadQueue1", DISPATCH_QUEUE_SERIAL);//注意queue對(duì)象不是指針類型
    //創(chuàng)建多個(gè)線程用于填充圖片
    for (int i=0; i<count; ++i) {
        //異步執(zhí)行隊(duì)列任務(wù)   任務(wù)按照順序執(zhí)行 并且會(huì)開啟子線程去執(zhí)行
        dispatch_async(serialQueue, ^{
            [self loadImage:[NSNumber numberWithInt:i]];
        });
        
    }
    //非ARC環(huán)境請(qǐng)釋放
    //    dispatch_release(seriQueue);
}
效果圖.gif
Concurrent Diapatch Queue 并發(fā)隊(duì)列

與串行隊(duì)列剛好相反罐柳,他不會(huì)存在任務(wù)間的相互依賴。

創(chuàng)建一個(gè)并發(fā)隊(duì)列:

dispatch_queue_t concurrentDispatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);

比較2個(gè)隊(duì)列的創(chuàng)建狰住,我們發(fā)現(xiàn)只有第二個(gè)參數(shù)從DISPATCH_QUEUE_SERIAL變成了對(duì)應(yīng)的DISPATCH_QUEUE_CONCURRENT张吉,其他完全一樣。

用同一段代碼催植,換一種隊(duì)列我們來比較一下效果:

dispatch_async(concurrentDispatchQueue, ^{
    NSLog(@"1");
});
dispatch_async(concurrentDispatchQueue, ^{
    sleep(2);
    NSLog(@"2");
});
dispatch_async(concurrentDispatchQueue, ^{
    sleep(1);
    NSLog(@"3");
});
輸出的log:
2016-03-07 10:42:38.289 GCD[2260:72557] 1
2016-03-07 10:42:39.291 GCD[2260:72559] 3
2016-03-07 10:42:40.293 GCD[2260:72556] 2

結(jié)論:我們發(fā)現(xiàn)肮蛹,log的輸出在3個(gè)不同編號(hào)的線程中進(jìn)行,而且相互不依賴创南,不阻塞伦忠。并且任務(wù)的執(zhí)行順序是無序的。

修改串行隊(duì)列加載圖片部分的代碼

-(void)loadImageWithMultiThread{
    int count=ROW_COUNT*COLUMN_COUNT;
    
    /*取得全局隊(duì)列
     第一個(gè)參數(shù):線程優(yōu)先級(jí)
     第二個(gè)參數(shù):標(biāo)記參數(shù)稿辙,目前沒有用昆码,一般傳入0
    */
    dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //創(chuàng)建多個(gè)線程用于填充圖片
    for (int i=0; i<count; ++i) {
        //異步執(zhí)行隊(duì)列任務(wù)
        dispatch_async(globalQueue, ^{
            [self loadImage:[NSNumber numberWithInt:i]];
        });
    }
}

運(yùn)行效果如下


效果圖.gif
Global Queue & Main Queue

這是系統(tǒng)為我們準(zhǔn)備的2個(gè)隊(duì)列:

Global Queue其實(shí)就是系統(tǒng)創(chuàng)建的Concurrent Diapatch Queue
Main Queue 其實(shí)就是系統(tǒng)創(chuàng)建的位于主線程的Serial Diapatch Queue
通常情況我們會(huì)把這2個(gè)隊(duì)列放在一起使用,也是我們最常用的開異步線程-執(zhí)行異步任務(wù)-回主線程的一種方式

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"異步線程");
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"異步主線程");
    });
});

通過上面的代碼我們發(fā)現(xiàn)了2個(gè)有意思的點(diǎn):
dispatch_get_global_queue存在優(yōu)先級(jí)邓深,沒錯(cuò)未桥,他一共有4個(gè)優(yōu)先級(jí):

#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    NSLog(@"4");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
    NSLog(@"3");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"2");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    NSLog(@"1");
});

在指定優(yōu)先級(jí)之后笔刹,同一個(gè)隊(duì)列會(huì)按照這個(gè)優(yōu)先級(jí)執(zhí)行芥备,打印的順序?yàn)?、2舌菜、3萌壳、4,當(dāng)然這不是串行隊(duì)列日月,所以不存在絕對(duì)回調(diào)先后袱瓮。(設(shè)置優(yōu)先級(jí)就是說可以優(yōu)先執(zhí)行某個(gè)任務(wù)但是這個(gè)任務(wù)會(huì)不會(huì)比其他任務(wù)先執(zhí)行完不能確定)

異步主線程
在日常工作中,除了在其他線程返回主線程的時(shí)候需要用這個(gè)方法爱咬,還有一些時(shí)候我們?cè)谥骶€程中直接調(diào)用異步主線程尺借,這是利用dispatch_async的特性:block中的任務(wù)會(huì)放在主線程本次runloop之后返回。這樣精拟,有些存在先后順序的問題就可以得到解決了燎斩。

說完了隊(duì)列虱歪,我們?cè)僬f說GCD提供的一些操作隊(duì)列的方法

dispatch_set_target_queue

剛剛我們說了系統(tǒng)的Global Queue是可以指定優(yōu)先級(jí)的,那我們?nèi)绾谓o自己創(chuàng)建的隊(duì)列執(zhí)行優(yōu)先級(jí)呢栅表?這里我們就可以用到dispatch_set_target_queue這個(gè)方法:

dispatch_queue_t serialDispatchQueue=dispatch_queue_create("com.test.queue", NULL);
dispatch_queue_t dispatchgetglobalqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_set_target_queue(serialDispatchQueue, dispatchgetglobalqueue);
dispatch_async(serialDispatchQueue, ^{
    NSLog(@"我優(yōu)先級(jí)低笋鄙,先讓讓");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"我優(yōu)先級(jí)高,我先block");
});

我把自己創(chuàng)建的隊(duì)列塞到了系統(tǒng)提供的global_queue隊(duì)列中,我們可以理解為:我們自己創(chuàng)建的queue其實(shí)是位于global_queue中執(zhí)行,所以改變global_queue的優(yōu)先級(jí)怪瓶,也就改變了我們自己所創(chuàng)建的queue的優(yōu)先級(jí)萧落。所以我們常用這種方式來管理子隊(duì)列。

dispatch_after

這個(gè)是最常用的洗贰,用來延遲執(zhí)行的GCD方法找岖,因?yàn)樵谥骶€程中我們不能用sleep來延遲方法的調(diào)用,所以用它是最合適的敛滋,我們做一個(gè)簡單的例子:

NSLog(@"小破孩-波波1");
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    NSLog(@"小破孩-波波2");
});

輸出的結(jié)果:

2016-03-07 11:25:06.019 GCD[2443:95722] 小破孩-波波1
2016-03-07 11:25:08.019 GCD[2443:95722] 小破孩-波波2
我們看到他就是在主線程宣增,就是剛好延遲了2秒,當(dāng)然矛缨,我說這個(gè)2秒并不是絕對(duì)的爹脾,為什么這么說?還記得我之前在介紹dispatch_async這個(gè)特性的時(shí)候提到的嗎箕昭?他的block中方法的執(zhí)行會(huì)放在主線程runloop之后灵妨,所以,如果此時(shí)runloop周期較長的時(shí)候落竹,可能會(huì)有一些時(shí)差產(chǎn)生泌霍。
dispatch_group

當(dāng)我們需要監(jiān)聽一個(gè)并發(fā)隊(duì)列中,所有任務(wù)都完成了述召,就可以用到這個(gè)group朱转,因?yàn)椴l(fā)隊(duì)列你并不知道哪一個(gè)是最后執(zhí)行的,所以以單獨(dú)一個(gè)任務(wù)是無法監(jiān)聽到這個(gè)點(diǎn)的积暖,如果把這些單任務(wù)都放到同一個(gè)group藤为,那么,我們就能通過dispatch_group_notify方法知道什么時(shí)候這些任務(wù)全部執(zhí)行完成了夺刑。

dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group=dispatch_group_create();
dispatch_group_async(group, queue, ^{NSLog(@"0");});
dispatch_group_async(group, queue, ^{NSLog(@"1");});
dispatch_group_async(group, queue, ^{NSLog(@"2");});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"down");});

在例子中缅疟,我把3個(gè)log分別放在并發(fā)隊(duì)列中,通過把這個(gè)并發(fā)隊(duì)列任務(wù)統(tǒng)一加入group中遍愿,group每次runloop的時(shí)候都會(huì)調(diào)用一個(gè)方法dispatch_group_wait(group, DISPATCH_TIME_NOW)存淫,用來檢查group中的任務(wù)是否已經(jīng)完成,如果已經(jīng)完成了沼填,那么會(huì)執(zhí)行dispatch_group_notify的block桅咆,輸出’down’看一下運(yùn)行結(jié)果:

2016-03-07 14:21:58.647 GCD[9424:156388] 2
2016-03-07 14:21:58.647 GCD[9424:156382] 0
2016-03-07 14:21:58.647 GCD[9424:156385] 1
2016-03-07 14:21:58.650 GCD[9424:156324] down

此方法的作用是在并發(fā)隊(duì)列中,完成在它之前提交到隊(duì)列中的任務(wù)后打斷坞笙,單獨(dú)執(zhí)行其block岩饼,并在執(zhí)行完成之后才能繼續(xù)執(zhí)行在他之后提交到隊(duì)列中的任務(wù):

dispatch_queue_t concurrentDispatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"0");});
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"1");});
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"2");});
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"3");});
dispatch_barrier_async(concurrentDispatchQueue, ^{sleep(1); NSLog(@"4");});
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"5");});
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"6");});
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"7");});
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"8");});

輸出的結(jié)果為:

2016-03-07 14:45:32.410 GCD[10079:169655] 1
2016-03-07 14:45:32.410 GCD[10079:169658] 2
2016-03-07 14:45:32.410 GCD[10079:169656] 0
2016-03-07 14:45:32.410 GCD[10079:169661] 3
2016-03-07 14:45:33.414 GCD[10079:169661] 4
2016-03-07 14:45:33.415 GCD[10079:169661] 5
2016-03-07 14:45:33.415 GCD[10079:169658] 6
2016-03-07 14:45:33.415 GCD[10079:169655] 8
2016-03-07 14:45:33.415 GCD[10079:169662] 7

總結(jié):4之后的任務(wù)在我線程sleep之后才執(zhí)行刽脖,這其實(shí)就起到了一個(gè)線程鎖的作用,在多個(gè)線程同時(shí)操作一個(gè)對(duì)象的時(shí)候忌愚,讀可以放在并發(fā)進(jìn)行曲管,當(dāng)寫的時(shí)候,我們就可以用dispatch_barrier_async方法硕糊,效果杠杠的院水。

dispatch_sync

dispatch_sync 會(huì)在當(dāng)前線程執(zhí)行隊(duì)列,并且阻塞當(dāng)前線程中之后運(yùn)行的代碼简十,所以檬某,同步線程非常有可能導(dǎo)致死鎖現(xiàn)象,我們這邊就舉一個(gè)死鎖的例子螟蝙,直接在主線程調(diào)用以下代碼:

dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"有沒有同步主線程?");
});

根據(jù)FIFO(先進(jìn)先出)的原則恢恼,block里面的代碼應(yīng)該在主線程此次runloop后執(zhí)行,但是由于他是同步隊(duì)列胰默,所有他之后的代碼會(huì)等待其執(zhí)行完成后才能繼續(xù)執(zhí)行场斑,2者相互等待,所以就出現(xiàn)了死鎖牵署。

我們?cè)倥e一個(gè)比較特殊的例子:

dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{sleep(1);NSLog(@"1");});
dispatch_sync(queue, ^{sleep(1);NSLog(@"2");});
dispatch_sync(queue, ^{sleep(1);NSLog(@"3");});
NSLog(@"4");

其打印結(jié)果為:

2016-03-07 17:15:48.124 GCD[14198:272683] 1
2016-03-07 17:15:49.125 GCD[14198:272683] 2
2016-03-07 17:15:50.126 GCD[14198:272683] 3
2016-03-07 17:15:50.126 GCD[14198:272683] 4

從線程編號(hào)中我們發(fā)現(xiàn)漏隐,同步方法沒有去開新的線程,而是在當(dāng)前線程中執(zhí)行隊(duì)列奴迅,會(huì)有人問青责,上文說dispatch_get_global_queue不是并發(fā)隊(duì)列,并發(fā)隊(duì)列不是應(yīng)該會(huì)在開啟多個(gè)線程嗎取具?這個(gè)前提是用異步方法脖隶。GCD其實(shí)是弱化了線程的管理,強(qiáng)化了隊(duì)列管理暇检,這使我們理解變得比較形象产阱。

dispatch_apply

這個(gè)方法用于無序查找,在一個(gè)數(shù)組中占哟,我們能開啟多個(gè)線程來查找所需要的值心墅,我這邊也舉個(gè)例子:

NSArray *array=[[NSArray alloc]initWithObjects:@"0",@"1",@"2",@"3",@"4",@"5",@"6", nil];
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply([array count], queue, ^(size_t index) {
    NSLog(@"%zu=%@",index,[array objectAtIndex:index]);
});
NSLog(@"阻塞");

輸出結(jié)果:

2016-03-07 17:36:50.726 GCD[14318:291701] 1=1
2016-03-07 17:36:50.726 GCD[14318:291705] 0=0
2016-03-07 17:36:50.726 GCD[14318:291783] 3=3
2016-03-07 17:36:50.726 GCD[14318:291782] 2=2
2016-03-07 17:36:50.726 GCD[14318:291784] 5=5
2016-03-07 17:36:50.726 GCD[14318:291627] 4=4
2016-03-07 17:36:50.726 GCD[14318:291785] 6=6
2016-03-07 17:36:50.727 GCD[14318:291627] 阻塞
通過輸出log,我們發(fā)現(xiàn)這個(gè)方法雖然會(huì)開啟多個(gè)線程來遍歷這個(gè)數(shù)組榨乎,但是在遍歷完成之前會(huì)阻塞主線程。
dispatch_suspend & dispatch_resume

隊(duì)列掛起和恢復(fù)瘫筐,這個(gè)沒什么好說的蜜暑,直接上代碼:

dispatch_queue_t concurrentDispatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentDispatchQueue, ^{serialDispatchQueue
    for (int i=0; i<100; i++)
    {
        NSLog(@"%i",i);
        if (i==50)
        {
            NSLog(@"-----------------------------------");
            dispatch_suspend(concurrentDispatchQueue);
            sleep(3);
            dispatch_async(dispatch_get_main_queue(), ^{
                dispatch_resume(concurrentDispatchQueue);
            });
        }
    }
});

我們甚至可以在不同的線程對(duì)這個(gè)隊(duì)列進(jìn)行掛起和恢復(fù),因?yàn)镚CD是對(duì)隊(duì)列的管理策肝。

Semaphore

我們可以通過設(shè)置信號(hào)量的大小肛捍,來解決并發(fā)過多導(dǎo)致資源吃緊的情況隐绵,以單核CPU做并發(fā)為例,一個(gè)CPU永遠(yuǎn)只能干一件事情拙毫,那如何同時(shí)處理多個(gè)事件呢依许,聰明的內(nèi)核工程師讓CPU干第一件事情,一定時(shí)間后停下來缀蹄,存取進(jìn)度峭跳,干第二件事情以此類推,所以如果開啟非常多的線程缺前,單核CPU會(huì)變得非常吃力蛀醉,即使多核CPU,核心數(shù)也是有限的衅码,所以合理分配線程拯刁,變得至關(guān)重要,那么如何發(fā)揮多核CPU的性能呢逝段?如果讓一個(gè)核心模擬傳很多線程垛玻,經(jīng)常干一半放下干另一件事情,那效率也會(huì)變低奶躯,所以我們要合理安排夭谤,將單一任務(wù)或者一組相關(guān)任務(wù)并發(fā)至全局隊(duì)列中運(yùn)算或者將多個(gè)不相關(guān)的任務(wù)或者關(guān)聯(lián)不緊密的任務(wù)并發(fā)至用戶隊(duì)列中運(yùn)算,所以用好信號(hào)量巫糙,合理分配CPU資源朗儒,程序也能得到優(yōu)化,當(dāng)日常使用中参淹,信號(hào)量也許我們只起到了一個(gè)計(jì)數(shù)的作用醉锄,真的有點(diǎn)大材小用。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);//為了讓一次輸出10個(gè)浙值,初始信號(hào)量為10
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i <100; i++)
{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//每進(jìn)來1次恳不,信號(hào)量-1;進(jìn)來10次后就一直hold住,直到信號(hào)量大于0开呐;
    dispatch_async(queue, ^{
        NSLog(@"%i",i);
        sleep(2);
        dispatch_semaphore_signal(semaphore);//由于這里只是log,所以處理速度非逞萄快,我就模擬2秒后信號(hào)量+1;
    });
} 
dispatch_once

這個(gè)函數(shù)一般是用來做一個(gè)真的單例筐付,也是非常常用的卵惦,在這里就舉一個(gè)單例的例子吧:

static SingletonTimer * instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    instance = [[SingletonTimer alloc] init];
});

return instance;

參考資料如下:
http://pingguohe.net/2016/03/07/GCD-is-so-easy.html
iOS開發(fā)系列--并行開發(fā)其實(shí)很容易
http://blog.jobbole.com/69019/
https://segmentfault.com/a/1190000006612189

文章中對(duì)應(yīng)的demo鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瓦戚,隨后出現(xiàn)的幾起案子沮尿,更是在濱河造成了極大的恐慌,老刑警劉巖较解,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件畜疾,死亡現(xiàn)場(chǎng)離奇詭異赴邻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)啡捶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門姥敛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瞎暑,你說我怎么就攤上這事彤敛。” “怎么了金顿?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵臊泌,是天一觀的道長。 經(jīng)常有香客問我揍拆,道長渠概,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任嫂拴,我火速辦了婚禮播揪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘筒狠。我一直安慰自己猪狈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布辩恼。 她就那樣靜靜地躺著雇庙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪灶伊。 梳的紋絲不亂的頭發(fā)上疆前,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音聘萨,去河邊找鬼渐苏。 笑死意乓,一個(gè)胖子當(dāng)著我的面吹牛亩冬,可吹牛的內(nèi)容都是我干的务冕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼翘贮,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼赊窥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起择膝,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤誓琼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后肴捉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腹侣,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年齿穗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了傲隶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窃页,死狀恐怖跺株,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脖卖,我是刑警寧澤乒省,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站畦木,受9級(jí)特大地震影響袖扛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜十籍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一蛆封、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧勾栗,春花似錦惨篱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至界牡,卻和暖如春簿寂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背欢揖。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工陶耍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人她混。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓烈钞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坤按。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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