iOS多线程之GCD使用

一.GCD简介

GCD(Grand Central Dispatch) 是iOS系统一套多线程操作API。字面意思既是宏观全局派发。相比于其他多线程API,GCD操作简单,功能强大,且会自动充分利用系统硬件资源。操作简单是因为我们无需管理线程,只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue)。GCD会负责创建线程和调度任务,由系统直接提供线程管理。功能强大是指系统封装好了很多C函数,调用这些C函数就可以实现线程操作中的绝大多数功能。
GCD里很重要的一个概念是队列queue,队列是一种里面的元素按先进先出规则的线性结构。我们可以把任务(往往都是block)往队列里添加,系统会自动去队里里取出任务,然后根据当前的队列是并行队列 还是串行 队列,响应的去分配到线程执行。
队列相当于我们与系统交互的媒介,我们把任务往队列里扔,系统去队列里取出任务,开辟线程,分配任务。队列有并行队列与串行队列之分,不同队列有不同表现形式。


二.并行串行、同步异步介绍

学习GCD,首先要明白两组概念,并行和串行、同步与异步
并行和串行比较好理解,按字面意思,并行指的并发执行,也就是开辟多个线程同时去执行队列里任务,串行的表现形式就是添加到队列里的任务会同时执行,因此后添加进队列的任务可能会先完成,而先添加进队列的任务不一定就先完成,串行则是指队里中的任务严格按照添加进队列的顺序先后执行。
同步异步主要是指是否阻塞当前代码执行, 等待队列里的任务都执行完才可以继续执行。同步执行会先执行队列里的任务,执行完后等待dispatch_sync函数返回才能继续执行下面操作,异步执行不需要等待队列任务执行完成,将任务添加到队列后,dispatch_sync函数马上返回,继续执行下面操作,同时另外开辟新线程去执行队列里的任务。同步执行时候 系统会尽量做到不开辟新线程,只在当前线程里执行队列中的任务。

三.GCD使用

使用GCD很简单,两步即可完成,
1.首先创建队列,
2.然后将任务放到队列里。

1.创建队列

创建队列 可以使用dispatch_queue_create来创建队列,需要传入两个参数,第一个参数表示队列的唯一标识符,用于唯一标志队列,可以为空;第二个参数用来识别是串行队列还是并行队列。DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并行队列。默认NULL表示串行队列。

// 串行队列的创建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL); 
// 并行队列的创建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT

系统为了方便开发使用,已经定义好了一个全局并发队列,dispatch_get_global_queue,创建时候有两个参数,第一个参数表示队列优先级,一般用默认优先级DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数由系统预留做以后使用,现在没用,可以先用0表示。

2.向队列里分配任务,任务以block的形式被添加到队列中

使用同步或者异步两种方式分配任务

// 同步执行任务创建方法 dispatch_sync(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 所需要执行的任务 });
// 异步执行任务创建方法 dispatch_async(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 所需要执行的任务 });

其中,任务既可以是临时声明的block,也可以是提前定义好的block。
这个block是dispatch_async和dispatch_sync的参数

@interface ViewController ()

@property (nonatomic, copy) dispatch_block_t block;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //定义队列里需要执行的block,即任务
    _block = ^(){
    
        for (int i = 0; i < 10 ; i++) {
            
            NSLog(@"%d-------%@",i,[NSThread currentThread]);
        }
        
    };
    
    //创建队列  并行队列
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
    
    //异步执行创建好的队列
    dispatch_async(queue,_block);

    NSLog(@"等不等我");
}

运行结果如下

2017-09-13 00:12:13.995 GCD[1148:97167] 等不等我
2017-09-13 00:12:13.995 GCD[1148:97384] 0-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.997 GCD[1148:97384] 1-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.998 GCD[1148:97384] 2-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.998 GCD[1148:97384] 3-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.999 GCD[1148:97384] 4-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.999 GCD[1148:97384] 5-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.999 GCD[1148:97384] 6-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:14.000 GCD[1148:97384] 7-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:14.000 GCD[1148:97384] 8-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:14.003 GCD[1148:97384] 9-------<NSThread: 0x600000265400>{number = 3, name = (null)}

可以看到使用符合dispatch_block_t 形式的block都可以往队列里去添加,而dispatch_block_t的类型如下图所示

dispatch_block_t类型.png

可以看到,它就是一个参数与返回值都为空的block。

3.四种情况

两种队列和两种执行方式组合起来有四种效果,分别是同步并行 同步串行 异步并行 和异步串行,
四种方式分别有不同的效果。在这写条件里,执行方式起主导作用,也就是同步和异步执行起决定因素。

1)同步并行
- (IBAction)syncconcurrence:(id)sender {
    
    NSLog(@"开始");
    
    //创建并行队列
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    //向队列中添加任务一
    dispatch_sync(queue, _block);
    
    //向队列中添加任务二
    dispatch_sync(queue, _block);
    
    //向队列中添加任务三
    dispatch_sync(queue, _block);
    
    NSLog(@"等不等我");
}

运行效果如下

2017-09-13 00:25:32.730 GCD[1181:107620] 开始
2017-09-13 00:25:32.731 GCD[1181:107620] 0-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.731 GCD[1181:107620] 1-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.731 GCD[1181:107620] 0-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.731 GCD[1181:107620] 1-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.732 GCD[1181:107620] 0-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.732 GCD[1181:107620] 1-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.733 GCD[1181:107620] 等不等我

可以看到结果是按添加到队列里的顺序,依次在主线程执行。
同步意味着阻塞,等队列里的任务执行完 才能继续往下执行NSLog(@"等不等我")这句代码,并且不会开辟新线程,尽量在原有线程里执行队列里的任务,虽然队列是并发队列,队列想要系统去开辟多个线程去执行,但是前面的执行方式已经限定,系统不能开辟新线程,只能在原有线程里面去执行队列里的任务,结果跟单线程一样,所以最终结果是依次执行队列里的任务。

2)同步串行
//同步串行
- (IBAction)syncseria:(id)sender {
    
    NSLog(@"开始");
    
    //创建并行队列 这里使用NULL缺省表示创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
    
    //向队列中添加任务一
    dispatch_sync(queue, _block);
    
    //向队列中添加任务二
    dispatch_sync(queue, _block);
    
    //向队列中添加任务三
    dispatch_sync(queue, _block);
    
    NSLog(@"等不等我");

}

运行结果如下

2017-09-13 00:29:42.657 GCD[1196:110692] 开始
2017-09-13 00:29:42.657 GCD[1196:110692] 0-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.657 GCD[1196:110692] 1-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.658 GCD[1196:110692] 0-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.659 GCD[1196:110692] 1-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.659 GCD[1196:110692] 0-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.660 GCD[1196:110692] 1-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.660 GCD[1196:110692] 等不等我

同步的意义同上,串行意味着按添加到队列里的顺序去执行队列里的任务,所以该组合效果与上面的效果是相同的。

3)异步并行
//异步并行
- (IBAction)asyncconcurrence:(id)sender {
    
    
    NSLog(@"开始");
    
    //创建并行队列
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    //向队列中添加任务一
    dispatch_async(queue, _block);
    
    //向队列中添加任务二
    dispatch_async(queue, _block);
    
    //向队列中添加任务三
    dispatch_async(queue, _block);
    
    NSLog(@"等不等我");

    
}
2017-09-13 00:53:26.554 GCD[1251:127458] 开始
2017-09-13 00:53:26.554 GCD[1251:127458] 等不等我
2017-09-13 00:53:26.554 GCD[1251:128289] 0-------<NSThread: 0x60800026e6c0>{number = 4, name = (null)}
2017-09-13 00:53:26.554 GCD[1251:128296] 0-------<NSThread: 0x600000079c80>{number = 5, name = (null)}
2017-09-13 00:53:26.554 GCD[1251:128297] 0-------<NSThread: 0x60800026da00>{number = 6, name = (null)}
2017-09-13 00:53:26.557 GCD[1251:128289] 1-------<NSThread: 0x60800026e6c0>{number = 4, name = (null)}
2017-09-13 00:53:26.558 GCD[1251:128296] 1-------<NSThread: 0x600000079c80>{number = 5, name = (null)}
2017-09-13 00:53:26.559 GCD[1251:128297] 1-------<NSThread: 0x60800026da00>{number = 6, name = (null)}

异步并行执行不会阻塞当前代码执行,系统将任务都添加到队列后马上继续执行下面的代码,由系统去队列里取出任务,然后开辟数个线程(线程数量不确定,由系统决定)去执行队列里的任务。

4)异步串行
//异步串行
- (IBAction)asyncseria:(id)sender {
    

    NSLog(@"开始");
    
    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
    
    //向队列中添加任务一
    dispatch_async(queue, _block);
    
    //向队列中添加任务二
    dispatch_async(queue, _block);
    
    //向队列中添加任务三
    dispatch_async(queue, _block);
    
    NSLog(@"等不等我");

    
}

运行效果如下

2017-09-13 00:53:44.740 GCD[1251:127458] 开始
2017-09-13 00:53:44.740 GCD[1251:127458] 等不等我
2017-09-13 00:53:44.741 GCD[1251:128663] 0-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
2017-09-13 00:53:44.741 GCD[1251:128663] 1-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
2017-09-13 00:53:44.742 GCD[1251:128663] 0-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
2017-09-13 00:53:44.743 GCD[1251:128663] 1-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
2017-09-13 00:53:44.743 GCD[1251:128663] 0-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
2017-09-13 00:53:44.743 GCD[1251:128663] 1-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}

与异步并行的相同点是都不会阻塞当前的代码执行,系统将任务都添加到队列后马上继续执行下面的代码,由系统去队里里取出任务,开辟新线程去执行队列里的任务。区别是串行执行时候,队列里的任务只能按顺序依次执行,所以系统仅仅开辟一个新线程就可以完成任务。

四种组合,在实际开发中应当如何选取?
同步方式使用较少,因为同步方式会阻塞当前线程,并且按顺序执行队列任务,这样的效果跟没有使用队列的情况下并没有什么差别,同步执行也不会开辟新线程, 使用意义不是很大。
异步使用场景较多,尤其是涉及到耗时操作,比如网络请求。在发出网络请求时候,我们肯定不能让主线程等待网络请求结果返回在继续执行,因此,这里要用异步dispatch_async请求。

串行队列
    //创建并行队列
    dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
    
    
    dispatch_async(queue, ^{
       
        //获取开始执行任务的相对时间  注意这个单位是纳秒
        uint64_t timeStrat = mach_absolute_time();
        NSLog(@"%llu",timeStrat);
        
        //模拟网络请求 2秒后完成
        sleep(2);
        NSLog(@"已完成任务1");
        
        //回到主队列 也就是主线程里进行UI操作
        dispatch_async(dispatch_get_main_queue(), ^{
            
            self.view.backgroundColor = [UIColor redColor];
        });
        
    });
    
    dispatch_async(queue, ^{
        
        //模拟网络请求 2秒后完成
        sleep(2);
        NSLog(@"已完成任务2");
        
        //回到主队列 也就是主线程里进行UI操作
        dispatch_async(dispatch_get_main_queue(), ^{
            
            self.view.backgroundColor = [UIColor greenColor];
        });
        
    });

    dispatch_async(queue, ^{
        
        //模拟网络请求 2秒后完成
        sleep(2);
        NSLog(@"已完成任务3");
        
        //回到主队列 也就是主线程里进行UI操作
        dispatch_async(dispatch_get_main_queue(), ^{
            
            self.view.backgroundColor = [UIColor purpleColor];
        });
        
        //获取结束任务的相对时间 注意这个单位是纳秒
        uint64_t timeEnd = mach_absolute_time();
        NSLog(@"%llu",timeEnd);
    });

执行结果如下:

2017-09-13 15:58:25.534 GCD[3052:231602] 24762306847072
2017-09-13 15:58:27.536 GCD[3052:231602] 已完成任务1
2017-09-13 15:58:29.537 GCD[3052:231602] 已完成任务2
2017-09-13 15:58:31.541 GCD[3052:231602] 已完成任务3
2017-09-13 15:58:31.541 GCD[3052:231602] 24768313894166

可以看到串行队列会按任务的添加顺序依次执行。每个任务会耗时2秒,完成队列所有任务要花费的时间约为6秒。

并行队列

仅需要把创建队列时候的第二个参数设置为DISPATCH_QUEUE_CONCURRENT即可

//创建并行队列
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    
    dispatch_async(queue, ^{
       
        //模拟网络请求 2秒后完成
        sleep(2);
        NSLog(@"已完成任务1");
        
        //回到主队列 也就是主线程里进行UI操作
        dispatch_async(dispatch_get_main_queue(), ^{
            
            self.view.backgroundColor = [UIColor redColor];
        });
        
    });
    
    dispatch_async(queue, ^{
        
        //模拟网络请求 2秒后完成
        sleep(2);
        NSLog(@"已完成任务2");
        
        //回到主队列 也就是主线程里进行UI操作
        dispatch_async(dispatch_get_main_queue(), ^{
            
            self.view.backgroundColor = [UIColor greenColor];
        });
        
    });

    dispatch_async(queue, ^{
        
        //模拟网络请求 2秒后完成
        sleep(2);
        NSLog(@"已完成任务3");
        
        //回到主队列 也就是主线程里进行UI操作
        dispatch_async(dispatch_get_main_queue(), ^{
            
            self.view.backgroundColor = [UIColor purpleColor];
        });
        
    });

执行效果如下

2017-09-13 15:51:43.429 GCD[3022:227333] 已完成任务2
2017-09-13 15:51:43.429 GCD[3022:227326] 已完成任务1
2017-09-13 15:51:43.429 GCD[3022:227332] 已完成任务3

可以看到并行不一定会按任务添加顺序执行,完全由系统取出任务,分配到不同线程去执行,所以先后顺序也会不同。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容