GCD看这个就够啦

什么是GCD

Grand Central Dispatch或者GCD,是一套低层API,提供了一种新 的方法来进行并发程序编写。从基本功能上讲,GCD有点像NSOperationQueue,他们都允许程序将任务切分为多个单一任务然后提交至工作队列来并发地或者串行地执行。GCD比之NSOpertionQueue更底层更高效,并且它不是Cocoa框架的一部分。
除了代码的平行执行能力,GCD还提供高度集成的事件控制系统。可以设置句柄来响应文件描述符、mach ports(Mach port 用于 OS X上的进程间通讯)、进程、计时器、信号、用户生成事件。这些句柄通过GCD来并发执行。
GCD的API很大程度上基于block,当然,GCD也可以脱离block来使用,比如使用传统c机制提供函数指针和上下文指针。实践证明,当配合block使用时,GCD非常简单易用且能发挥其最大能力。
你可以在Mac上敲命令“man dispatch”来获取GCD的文档。

GCD的优势

GCD提供很多超越传统多线程编程的优势:
易用: GCD比之thread跟简单易用。由于GCD基于work unit而非像thread那样基于运算,所以GCD可以控制诸如等待任务结束、监视文件描述符、周期执行代码以及工作挂起等任务。基于block的血统导致它能极为简单得在不同代码作用域之间传递上下文。
效率: GCD被实现得如此轻量和优雅,使得它在很多地方比之专门创建消耗资源的线程更实用且快速。这关系到易用性:导致GCD易用的原因有一部分在于你可以不用担心太多的效率问题而仅仅使用它就行了。
性能: GCD自动根据系统负载来增减线程数量,这就减少了上下文切换以及增加了计算效率。

下面来看一下几种线程的生成方式(Dispatch Queues)

1. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial", DISPATCH_QUEUE_SERIAL); //生成一个串行队列,队列中的block按照先进先出(FIFO)的顺序去执行,实际上为单线程执行。第一个参数是队列的名称,在调试程序时会非常有用,所有尽量不要重名了。  
  
2. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent", DISPATCH_QUEUE_CONCURRENT); //生成一个并发执行队列,block被分发到多个线程去执行  
  
3. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //获得程序进程缺省产生的并发队列,可设定优先级来选择高、中、低三个优先级队列。由于是系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。需要注意的是,三个队列不代表三个线程,可能会有更多的线程。并发队列可以根据实际情况来自动产生合理的线程数,也可理解为dispatch队列实现了一个线程池的管理,对于程序逻辑是透明的。

4. dispatch_queue_t queue = dispatch_get_main_queue(); //获得主线程的dispatch队列,实际是一个串行队列。同样无法控制主线程dispatch队列的执行继续或中断。

GCD中常见的队列函数

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t)); //重复执行block,需要注意的是这个方法是同步返回,也就是说等到所有block执行完毕才返回,如需异步返回则嵌套在dispatch_async中来使用。多个block的运行是否并发或串行执行也依赖queue的是否并发或串行。  
  
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); //这个函数可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行。  
  
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block); //同上,除了它是同步返回函数  
  
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); //延迟执行block 

dispatch_after

常见的延迟方法,在主线程中不能使用sleep,就得dispatch_after出场啦

NSLog(@"我要睡2秒");
    double delayInSeconds = 2.0;//延迟2秒
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        NSLog(@"睡醒了");
    });

double delayInSeconds = 2.0;

    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);

    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){

        // code to be executed on the main queue after delay
    });

dispatch_group

当我们需要监听一个并发队列中,所有任务都完成了,就可以用到这个group,因为并发队列你并不知道哪一个是最后执行的,所以以单独一个任务是无法监听到这个点的,如果把这些单任务都放到同一个group,那么,我们就能通过dispatch_group_notify方法知道什么时候这些任务全部执行完成了。

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个log分别放在并发队列中,通过把这个并发队列任务统一加入group中,group每次runloop的时候都会调用一个方法dispatch_group_wait(group, DISPATCH_TIME_NOW),用来检查group中的任务是否已经完成,如果已经完成了,那么会执行dispatch_group_notify的block

打印效果

2017-08-25 20:04:50.352 GCD的简单使用[41803:888331] 2
2017-08-25 20:04:50.352 GCD的简单使用[41803:888328] 1
2017-08-25 20:04:50.352 GCD的简单使用[41803:888347] 0
2017-08-25 20:04:50.359 GCD的简单使用[41803:888246] down

dispatch_barrier_async(栅栏函数)

这个方法在并发队列中前面的任务完成后再执行dispatch_barrier_async,dispatch_barrier_async执行完成,才执行后面的任务

dispatch_queue_t que =dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(que, ^{NSLog(@"0");});
    dispatch_async(que, ^{NSLog(@"1");});
    dispatch_async(que, ^{NSLog(@"2");});
    dispatch_async(que, ^{NSLog(@"3");});
    dispatch_barrier_async(que, ^{NSLog(@"我是一个栅栏");});
    dispatch_async(que, ^{NSLog(@"5");});
    dispatch_async(que, ^{NSLog(@"6");});
    dispatch_async(que, ^{NSLog(@"7");});
    dispatch_async(que, ^{NSLog(@"8");});

输出结果

2017-08-25 20:15:12.706 GCD的简单使用[41903:899729] 0
2017-08-25 20:15:12.706 GCD的简单使用[41903:899727] 1
2017-08-25 20:15:12.706 GCD的简单使用[41903:899726] 2
2017-08-25 20:15:12.707 GCD的简单使用[41903:899744] 3
2017-08-25 20:15:13.708 GCD的简单使用[41903:899744] 我是一个栅栏
2017-08-25 20:15:13.709 GCD的简单使用[41903:899744] 5
2017-08-25 20:15:13.709 GCD的简单使用[41903:899727] 6
2017-08-25 20:15:13.709 GCD的简单使用[41903:899729] 7
2017-08-25 20:15:13.709 GCD的简单使用[41903:899726] 8

dispatch_apply

这个方法用于无序查找,在一个数组中,我们能开启多个线程来查找所需要的值,记得放到异步去执行,要不然会阻塞主线程。

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_async(queue, ^{
        dispatch_apply([array count], queue, ^(size_t index) {
            NSLog(@"%zu=%@",index,[array objectAtIndex:index]);
        });
    });
    NSLog(@"主线程");

打印结果

2017-08-25 20:22:19.744 GCD的简单使用[42000:907091] 阻塞
2017-08-25 20:22:19.744 GCD的简单使用[42000:907126] 0=0
2017-08-25 20:22:19.744 GCD的简单使用[42000:907187] 1=1
2017-08-25 20:22:19.744 GCD的简单使用[42000:907127] 2=2
2017-08-25 20:22:19.744 GCD的简单使用[42000:907186] 3=3
2017-08-25 20:22:19.745 GCD的简单使用[42000:907126] 4=4
2017-08-25 20:22:19.745 GCD的简单使用[42000:907187] 5=5
2017-08-25 20:22:19.745 GCD的简单使用[42000:907127] 6=6

dispatch_suspend & dispatch_resume

线程的挂起和开始,下载的时候比较常用

dispatch_queue_t queue =dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (int i=0; i<100; i++)
        {
            NSLog(@"%i",i);
            if (i==50)
            {
                NSLog(@"----------------我要停下来歇会-------------------");
                dispatch_suspend(queue);
                sleep(5);
                dispatch_async(dispatch_get_main_queue(), ^{
                    dispatch_resume(queue);
                });
            }
        }
    });

打印结果

2017-08-25 20:27:50.671 GCD的简单使用[42079:912731] 50
2017-08-25 20:27:50.671 GCD的简单使用[42079:912731] ----------------我要停下来歇会-------------------
2017-08-25 20:27:55.677 GCD的简单使用[42079:912731] 51

dispatch_semaphore_t

我们可以通过设置信号量的大小,来解决并发过多导致资源吃紧的情况,以单核CPU做并发为例,一个CPU永远只能干一件事情,那如何同时处理多个事件呢,聪明的内核工程师让CPU干第一件事情,一定时间后停下来,存取进度,干第二件事情以此类推,所以如果开启非常多的线程,单核CPU会变得非常吃力,即使多核CPU,核心数也是有限的,所以合理分配线程,变得至关重要,那么如何发挥多核CPU的性能呢?如果让一个核心模拟传很多线程,经常干一半放下干另一件事情,那效率也会变低,所以我们要合理安排,将单一任务或者一组相关任务并发至全局队列中运算或者将多个不相关的任务或者关联不紧密的任务并发至用户队列中运算,所以用好信号量,合理分配CPU资源,程序也能得到优化,当日常使用中,信号量也许我们只起到了一个计数的作用,真的有点大材小用。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);//为了让一次输出10个,初始信号量为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);//每进来1次,信号量-1;进来10次后就一直hold住,直到信号量大于0;这里一直-1,一直减到0
        dispatch_async(queue, ^{
            NSLog(@"%i",i);
            sleep(2);
            dispatch_semaphore_signal(semaphore);//由于这里只是log,所以处理速度非常快,我就模拟2秒后信号量+1;这里是一直+1  一直加到10
        });
    }

dispatch_once

啥都不说啦写个单例压压惊

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

return instance;

全局的并发队列dispatch_get_global_queue

可以同时运行多个任务,每个任务的启动时间是按照加入queue的顺序,结束的顺序依赖各自的任务.

dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任务一:
    dispatch_async(myQueue, ^{
        NSLog(@"1");
    });
    //任务二:
    dispatch_async(myQueue, ^{
        NSLog(@"2");
    });
    //任务三:
    dispatch_async(myQueue, ^{
        NSLog(@"3");
    });

打印结果

执行时间是顺序的1.2.3,处理结果的时间是根据自己处理的任务大小来显示时间
2017-08-25 20:49:13.157 GCD的简单使用[42284:933722] 1
2017-08-25 20:49:13.157 GCD的简单使用[42284:933693] 2
2017-08-25 20:49:13.157 GCD的简单使用[42284:933692] 3

dispatch_set_target_queue转换队列

/**
  dispatch_set_target_queue
  通过dispatch_set_target_queue函数可以设置一个dispatch queue的优先级,或者指定一个dispatch source相应的事件处理提交到哪个queue上。
  它会把需要执行的任务对象指定到不同的队列中去处理,这个任务对象可以是dispatch队列,也可以是dispatch源。而且这个过程可以是动态的,可以实现队列的动态调度管理等等。比如说有两个队列dispatchA和dispatchB,这时把dispatchA指派到dispatchB:
  dispatch_set_target_queue(dispatchA, dispatchB);
  那么dispatchA上还未运行的block会放到dispatchB上,然后由dispatchB来进行管理运行。
 **/

  dispatch_set_target_queue(serialQ, globalQ);

dispatch_group_wait等待

dispatch_group_wait来等待这些任务完成。若任务已经全部完成或为空,则直接返回,否则等待所有任务完成后返回。

dispatch_group_t group = dispatch_group_create();
    
    //注意queue为DISPATCH_QUEUE_CONCURRENT 和 DISPATCH_QUEUE_SERIAL时当前的线程
    
    dispatch_queue_t queue = dispatch_queue_create([@"queue" UTF8String], DISPATCH_QUEUE_SERIAL);
    
    //每个dispatch_group_enter对应一个dispatch_group_leave完成group内所有的任务则发送通知
    
    //进入group
    
    dispatch_group_enter(group);
    
    dispatch_async(queue, ^{
        
        NSLog(@"task1~~~~~currentThread=%@~~~~~mainThread=%@", [NSThread currentThread], [NSThread mainThread]);
        
        //离开group
        
        dispatch_group_leave(group);
        
    });
    
    
    dispatch_group_enter(group);
    
    dispatch_async(queue, ^{
        
        NSLog(@"task2~~~~~currentThread=%@~~~~~mainThread=%@", [NSThread currentThread], [NSThread mainThread]);
        
        dispatch_group_leave(group);
        
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"任务已经全部完成啦after group wait");
    
    dispatch_group_notify(group, queue, ^{
        
        NSLog(@"是的确实完成啦allGroupFinish");
        
    });

打印结果

2017-08-25 21:10:17.743 GCD的简单使用[42425:950722] task1~~~~~currentThread=<NSThread: 0x608000262880>{number = 3, name = (null)}~~~~~mainThread=<NSThread: 0x600000079d80>{number = 1, name = (null)}
2017-08-25 21:10:17.743 GCD的简单使用[42425:950722] task2~~~~~currentThread=<NSThread: 0x608000262880>{number = 3, name = (null)}~~~~~mainThread=<NSThread: 0x600000079d80>{number = 1, name = (null)}
2017-08-25 21:10:17.743 GCD的简单使用[42425:950681] 任务已经全部完成啦after group wait
2017-08-25 21:10:17.743 GCD的简单使用[42425:950722] 是的确实完成啦allGroupFinish

代码地址https://github.com/yanglijunwang/GCD-

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

推荐阅读更多精彩内容