18、iOS底层分析 - GCD(一)基本概念与简单使用

GCD

介绍

        GCD 全程 Grand Central Dispatch,由C 语言实现,是苹果为多核的并行运行运算提出的解决方案,GCD 会自动利用更多的CPU 内核,自动管理线程的生命周期。GCD 实现的库是 libdispatch,可以在 Apple Open Source 下载 源码
创建线程、调度任务、销毁线程都是由GCD自己进行管理,只需要告诉GCD 想要执行什么任务,不需要编写任何线程管理代码。
 
 GCD中的两大核心
 任务:执行什么操作
 队列:用来存放任务
 
 GCD的官方说明

开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。
 定制任务:想要做的事情
 将任务添加到队列中


队列

dispatch_queue_t (队列)

在使用GCD 的时候,都避不开dispatch_queue_t (队列),可以说 queue 是使用 GCD 的基础和前提。
Dispatch Queue通过结构体和链表,被实现为FIFO(先进先出)队列。FIFO队列主要是负责管理通过 dispatch_async 等函数所追加的一系列 Blocks。所以我们可以理解为一旦我们在程序中由上到下追加了一组Blocks,那么排除掉 dispatch_after(延迟执行),其内部的追加过程是一个先进先出原则。
 
但是Block本身并不是直接加入到这个FIFO队列中,而是先加入Dispatch Continuation这一dispatch_continuation_t类型结构体中,然后再进入FIFO队列。该结构体用于记忆Block所属的Dispatch Group和其他一些信息,相当于一般常说的执行上下文(execution context)。

//创建队列
dispatch_queue_create(const char *label, dispatch_queue_attr_t attire);
创建一个queue(队列)
    label:用来标识 queue 的字符串
    attr:队列属性,DISPATCH_QUEUE_SERIAL 或 NULL 会创建串行队列, DISPATCH_QUEUE_CONCURRENT 为并行队列
//示例
dispatch_queue_t mySerialQueue = dispatch_queue_create("com.gcd.queueCreate.mySerialQueue", NULL);

创建队列
 1、第一个参数是队列名称,一般采用域名反转的命名规则,便于调试
 2、第二个参数用于区分创建串行队列还是并行队列。
    串行队列:传入 NULLDISPATCH_QUEUE_SERIAL
    并行队列:传入 DISPATCH_QUEUE_CONCURRENT
 3、优先级是默认优先级 即 DISPATCH_QUEUE_PRIORITY_DEFAULTD (0 优先级-默认)
 
 
 获取队列
 三种方式
创建队列也是一种获取队列的方式,除了创建队列,系统标准提供的还有 主队列(Main Dispatch Queue)全局队列(Global Dispatch Queue)

    1、获取主队列

    /*
     * Main Dispatch Queue 的获取方法
     */
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

主队列是在应用程序启动时,main()函数执行之前由系统自动创建的,这个queue被绑定在主线程上。主线程又常被称为 UI线程,所有的UI操作都应在这个线程中进行,主队列是一个串行队列。(当然在其他线程上操作UI是不会报错的,但是可能会有其他问题出现)

//    异步执行主队列刷新UI
    dispatch_async(dispatch_get_main_queue(), ^{
        self.view.backgroundColor = [UIColor grayColor];
    });

2、创建队列

    //创建一个并发队列
    dispatch_queue_t dataQueue1 = dispatch_queue_create("dataQueue", DISPATCH_QUEUE_CONCURRENT);
    //创建一个串行队列
    dispatch_queue_t dataQueue2 = dispatch_queue_create("dataQueue", DISPATCH_QUEUE_SERIAL);


    dispatch_async(dataQueue1, ^{
        NSLog(@"异步执行 并行队列 处理数据,然后通知主线程刷新UI");
        int a= 200;
        for (int i=0; i<100; i++) {
            a++;
            NSLog(@"异步执行 并行队列 %d",a);
        }
        //通知主线程去刷新UI
        [self performSelector:@selector(updataUI:) withObject:[NSString stringWithFormat:@"处理后的数据1 %d",a]];
    });
    dispatch_sync(dataQueue1, ^{
        NSLog(@"同步执行 并行队列 处理数据,然后通知主线程刷新UI");
        int a= 0;
        for (int i=0; i<100; i++) {
            a++;
            NSLog(@"同步执行 并行队列 %d",a);
        }
        [self performSelector:@selector(updataUI:) withObject:[NSString stringWithFormat:@"处理后的数据2 %d",a]];
    });

3、 获取全局队列
 dispatch_get_global_queue 文档

    /*
     * Global Dispatch Queue (高优先级)的获取方法
     */
    dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    
    /*
     * Global Dispatch Queue (默认优先级)的获取方法
     */
    dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    /*
     * Global Dispatch Queue (低优先级)的获取方法
     */
    dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    
    /*
     * Global Dispatch Queue (后台优先级)的获取方法
     */
    dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    //获取默认优先级的全局队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //获取优先级为后台优先级的全局队列
    dispatch_queue_t globalDefaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    //变更优先级
    dispatch_set_target_queue(dataQueue, globalDefaultQueue);

dispatch_set_target_queue 后面做分析。

 第一个参数是指明队列执行的优先级

//dispatch_get_global_queue(0, 0) 根据入参计算出索引,从_dispatch_root_queues[]数组中取出对应的queue:
struct dispatch_queue_global_s _dispatch_root_queues[] = {
    
    _DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, 0,
                               .dq_label = "com.apple.root.maintenance-qos",                             
                               .dq_serialnum = 4,
                               ),
    
    _DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
                               .dq_label = "com.apple.root.maintenance-qos.overcommit",                              
                               .dq_serialnum = 5,                              
                               ),
    
    _DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, 0,                    
                               .dq_label = "com.apple.root.background-qos",                        
                               .dq_serialnum = 6,                            
                               ),
    
    _DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,                          
                               .dq_label = "com.apple.root.background-qos.overcommit",                          
                               .dq_serialnum = 7,                           
                               ),
    
    _DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, 0,                        
                               .dq_label = "com.apple.root.utility-qos",                          
                               .dq_serialnum = 8,                           
                               ),
    
    _DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,                         
                               .dq_label = "com.apple.root.utility-qos.overcommit",                        
                               .dq_serialnum = 9,                             
                               ),
    
    _DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,                          
                               .dq_label = "com.apple.root.default-qos",                          
                               .dq_serialnum = 10,                         
                               ),
    
    _DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,                             
                               DISPATCH_PRIORITY_FLAG_FALLBACK | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,                        
                               .dq_label = "com.apple.root.default-qos.overcommit",                       
                               .dq_serialnum = 11,                           
                               ),
    
    _DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, 0,             
                               .dq_label = "com.apple.root.user-initiated-qos",                      
                               .dq_serialnum = 12,                      
                               ),
    
    _DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,                     
                               .dq_label = "com.apple.root.user-initiated-qos.overcommit",                       
                               .dq_serialnum = 13,                         
                               ),
    
    _DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, 0,                    
                               .dq_label = "com.apple.root.user-interactive-qos",                   
                               .dq_serialnum = 14,                  
                               ),
    
    _DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,               
                               .dq_label = "com.apple.root.user-interactive-qos.overcommit",                
                               .dq_serialnum = 15,                
                               ),
    
};
Queue Priority  QOS     说明
 DISPATCH_QUEUE_PRIORITY_HIGH   QOS_CLASS_USER_INITIATED  最高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT     QOS_CLASS_DEFAULT     默认优先级
DISPATCH_QUEUE_PRIORITY_LOW QOS_CLASS_UTILITY 低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND   QOS_CLASS_BACKGROUND   后台优先级
    NSLog(@"1、high  %@", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); //2
    NSLog(@"2、OVERCOMMIT  %@", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
    NSLog(@"3、DEFAULT  %@", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));//0
    NSLog(@"4、LOW  %@", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)); //-2
    NSLog(@"5、INITIATED  %@", dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 2));
    NSLog(@"6、BACKGROUND  %@", dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 2));
    NSLog(@"7、DEFAULT  %@", dispatch_get_global_queue(QOS_CLASS_DEFAULT, 2));
    NSLog(@"8、UTILITY  %@", dispatch_get_global_queue(QOS_CLASS_UTILITY, 2));
    NSLog(@"9、%@", dispatch_get_main_queue());


 QOS_CLASS_USER_INTERACTIVE:要求此类工作相对于系统上的其他工作以高优先级运行。指定这个QOS类是在几乎所有可用系统CPU和I/O带宽处于争用状态下运行的请求。这不是用于大型任务的节能QOS类。这个QOS类的使用应该仅限于与用户的关键交互,例如在主事件循环中处理事件、视图绘制、动画等。我们可以看出主线程对应的是QOS_CLASS_USER_INTERACTIVE。


 QOS_CLASS_USER_INITIATED:这类工作的优先级要求低于关键的用户交互工作,但相对高于系统上的其他工作。这不是用于大型任务的节能QOS类。它的使用应该限制在足够短的持续时间内,这样用户在等待结果时就不太可能切换任务。典型的用户发起的工作将通过显示占位符内容或模态用户界面来指示进度。dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)获取的就是QOS_CLASS_USER_INITIATED的。


 QOS_CLASS_DEFAULT:这类工作的优先级要求低于关键用户交互和用户发起的工作,但相对高于实用程序和后台任务。pthread_create()创建的线程如果没有指定QOS类的属性,则默认为QOS_CLASS_DEFAULT。qos_class是可以设置pthread优先级的。此QOS类值不打算用作工作分类,仅在传播或恢复系统提供的QOS类值时设置。我们用的比较多。


 QOS_CLASS_UTILITY:这类工作的优先级要求低于关键用户交互和用户发起的工作,但相对高于低级系统维护任务。使用这个QOS类表明工作应该以一种能量和热效率的方式运行。效用工作的进展可能会或不会向用户显示,但这种工作的效果是用户可见的。对应的是我们常用的DISPATCH_QUEUE_PRIORITY_LOW。


 QOS_CLASS_BACKGROUND:要求这些工作优先于其他工作运行。使用这个QOS类表明工作应该以最节能和最高效的方式运行。


 我们一般获取全局队列都用方法dispatch_get_global_queue。两个参数。第一个参数是优先级的。和上面对应。所以qos_class和DISPATCH_QUEUE_PRIORITY_HIGH都可以使用。第二个参数是用来控制是否overcommit.
 

队列的执行

 串行队列、并发队列、全局队列(并发)、主队列(串行)。
 执行的方法有:同步执行、异步执行
 同步:只能在当前线程中执行任务,不具备开启新线程的能力
 异步:可以在新的线程中执行任务,具备开启新线程的能力(具备但是不一定非要开启新的线程根据实际情况决定)

1、串行队列,同步执行

//    1、串行队列,同步执行
    dispatch_queue_t test1Queue = dispatch_queue_create("com.liujilou.queue1", NULL);//默认串行 NULL 或 DISPATCH_QUEUE_SERIAL
    for (int a1=0; a1<10; a1++) {
        dispatch_sync(test1Queue, ^{
//          打印一下当前线程和 a1
            NSLog(@"%@ - %d",[NSThread currentThread],a1);
        });
    }
    NSLog(@"%@ 串行队列  同步执行",[NSThread currentThread]);

/*
 结果
 2020-03-13 09:50:06.360033+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 0
 2020-03-13 09:50:06.360210+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 1
 2020-03-13 09:50:06.360365+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 2
 2020-03-13 09:50:06.360524+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 3
 2020-03-13 09:50:06.360644+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 4
 2020-03-13 09:50:06.360807+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 5
 2020-03-13 09:50:06.360917+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 6
 2020-03-13 09:50:06.361140+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 7
 2020-03-13 09:50:06.361400+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 8
 2020-03-13 09:50:06.361552+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} - 9
 2020-03-13 09:50:06.361826+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} 串行队列  同步执行
*/

在主线程执行,并且顺序执行,循环结束之后主线程的打印才输出。
    
2、串行队列,异步执行

//    2、串行队列,异步执行
    dispatch_queue_t test2Queue = dispatch_queue_create("com.liujilou.queue2", NULL);
    for (int a2=0; a2<10; a2++) {
        dispatch_async(test2Queue, ^{
            NSLog(@"%@ - %d",[NSThread currentThread],a2);
        });
    }
    NSLog(@"%@ 串行队列,异步执行",[NSThread currentThread]);
    NSLog(@"↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑   分割线   ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");

/*
 结果
 2020-03-13 09:50:06.362828+0800 filedome[51520:1015947] <NSThread: 0x60000254eec0>{number = 1, name = main} 串行队列,异步执行
 2020-03-13 09:50:06.362855+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 0
 2020-03-13 09:50:06.363099+0800 filedome[51520:1015947] ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑   分割线   ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
 2020-03-13 09:50:06.364672+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 1
 2020-03-13 09:50:06.364860+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 2
 2020-03-13 09:50:06.366343+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 3
 2020-03-13 09:50:06.382140+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 4
 2020-03-13 09:50:06.382908+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 5
 2020-03-13 09:50:06.383062+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 6
 2020-03-13 09:50:06.383246+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 7
 2020-03-13 09:50:06.383422+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 8
 2020-03-13 09:50:06.383584+0800 filedome[51520:1016014] <NSThread: 0x600002535880>{number = 3, name = (null)} - 9
 */

系统开辟了1条异步线程,且test2Queue 队列中的任务全部在开辟的线程中顺序执行。主线程的任务先后顺序不确定。一般情况下异步线程会有时间消耗,所以会先执行主线程的打印才会执行异步线程,如上打印出来的就是这样的一种情况,”串行队列,异步执行“这个先执行了,然后执行异步线程,然后中间又执行了”分割线“。

3、并发队列,同步执行

//    3、并发队列,同步执行
    dispatch_queue_t test3Queue = dispatch_queue_create("com.liujilou.queue3", DISPATCH_QUEUE_CONCURRENT);
    for (int a3=0; a3<10; a3++) {
        dispatch_sync(test3Queue, ^{
            NSLog(@"%@ - %d", [NSThread currentThread],a3);
        });
    }
    NSLog(@"%@  并发队列,同步执行",[NSThread currentThread]);
/*
 2020-03-13 10:12:17.439909+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 0
 2020-03-13 10:12:17.440074+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 1
 2020-03-13 10:12:17.440198+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 2
 2020-03-13 10:12:17.440302+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 3
 2020-03-13 10:12:17.440422+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 4
 2020-03-13 10:12:17.440549+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 5
 2020-03-13 10:12:17.440663+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 6
 2020-03-13 10:12:17.440798+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 7
 2020-03-13 10:12:17.441149+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 8
 2020-03-13 10:12:17.441387+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main} - 9
 2020-03-13 10:12:17.441713+0800 filedome[51920:1026742] <NSThread: 0x60000313a940>{number = 1, name = main}  并发队列,同步执行
 */

这和第1中情况一样。同步是按顺序执行,后面都要等待,不能开辟显得线程来执行,所以无论是串行还是并发队列只要是同步执行效果都一样。

4、并发队列,异步执行

//   4、并发队列,异步执行
    dispatch_queue_t test4Queue = dispatch_queue_create("com.liujilou.queue4", DISPATCH_QUEUE_CONCURRENT);
    for (int a4 =0; a4 <10; a4++) {
        dispatch_async(test4Queue, ^{
            NSLog(@"%@ - %d",[NSThread currentThread],a4);
        });
    }
    NSLog(@"%@  并发队列,异步执行",[NSThread currentThread]);
/*
 2020-03-13 10:15:38.682348+0800 filedome[51971:1029504] <NSThread: 0x60000195c080>{number = 1, name = main}  并发队列,异步执行
 2020-03-13 10:15:38.682382+0800 filedome[51971:1029629] <NSThread: 0x600001921b80>{number = 3, name = (null)} - 0
 2020-03-13 10:15:38.682387+0800 filedome[51971:1029626] <NSThread: 0x60000192d940>{number = 5, name = (null)} - 2
 2020-03-13 10:15:38.682390+0800 filedome[51971:1029628] <NSThread: 0x6000019d9b40>{number = 4, name = (null)} - 3
 2020-03-13 10:15:38.682401+0800 filedome[51971:1029627] <NSThread: 0x6000019d9a00>{number = 6, name = (null)} - 1
 2020-03-13 10:15:38.682513+0800 filedome[51971:1029629] <NSThread: 0x600001921b80>{number = 3, name = (null)} - 4
 2020-03-13 10:15:38.682524+0800 filedome[51971:1029626] <NSThread: 0x60000192d940>{number = 5, name = (null)} - 6
 2020-03-13 10:15:38.682524+0800 filedome[51971:1029628] <NSThread: 0x6000019d9b40>{number = 4, name = (null)} - 5
 2020-03-13 10:15:38.682585+0800 filedome[51971:1029627] <NSThread: 0x6000019d9a00>{number = 6, name = (null)} - 7
 2020-03-13 10:15:38.682632+0800 filedome[51971:1029626] <NSThread: 0x60000192d940>{number = 5, name = (null)} - 8
 2020-03-13 10:15:38.682677+0800 filedome[51971:1029629] <NSThread: 0x600001921b80>{number = 3, name = (null)} - 9
 开辟多条线程,无序执行。
 */

开辟多条线程,无序执行。
    
5、主队列,同步执行 (死锁,循环等待)

//    5、主队列,同步执行 (死锁,循环等待)
    NSLog(@"打印5-1  %@  之前的线程",[NSThread currentThread]);
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"打印5-2  %@ - sync",[NSThread currentThread]);
    });
    NSLog(@"打印5-3  %@  之后的线程",[NSThread currentThread]);


 会运行卡死,因为循环等待,主队列的东西需要等主线程执行完,而因为是同步执行不能开辟新的线程去执行,所以下面的的任务必须要等上面的任务执行完。
 打印5-1 正常执行 - 打印5-2 在主队列需要等待主线程的打印5-3 执行完(等待) -  打印5-3 之前是同步执行,所以打印5-3 需要等同步执行的主队列内任务执行完(也就是打印5-2)(等待)。打印5-2 和 5-3 相互等待,造成循环等待。

    
6、主队列,异步执行

//    6、主队列,异步执行
    for (int a6=0; a6<10; a6++) {
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"%@ - %d",[NSThread currentThread],a6);
        });
    }
    NSLog(@"%@ sleep",[NSThread currentThread]);
    [NSThread sleepForTimeInterval:2.0];
    NSLog(@"%@ 主队列,异步执行",[NSThread currentThread]);
/*
 2020-03-13 10:33:49.743024+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} sleep
 2020-03-13 10:33:51.743730+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} 主队列,异步执行
 2020-03-13 10:33:51.756412+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 0
 2020-03-13 10:33:51.756598+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 1
 2020-03-13 10:33:51.756728+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 2
 2020-03-13 10:33:51.756830+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 3
 2020-03-13 10:33:51.756931+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 4
 2020-03-13 10:33:51.757058+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 5
 2020-03-13 10:33:51.757180+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 6
 2020-03-13 10:33:51.757284+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 7
 2020-03-13 10:33:51.757410+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 8
 2020-03-13 10:33:51.757522+0800 filedome[52271:1044891] <NSThread: 0x600000fc52c0>{number = 1, name = main} - 9
 */

主线程休眠2秒之后再开始执行,循环会一直等待,主队列需要等主线程上的任务执行完才会执行。

7、同步任务的使用

//    7、同步任务的使用
    dispatch_queue_t test7Queue = dispatch_queue_create("com.liujilou.queue7", DISPATCH_QUEUE_CONCURRENT);
//    1、用户登录,必须要第一个执行
    dispatch_sync(test7Queue, ^{
        NSLog(@"%@ 用户登录",[NSThread currentThread]);
    });

//    2、查询数据
    dispatch_async(test7Queue, ^{
        NSLog(@"%@ 查询数据",[NSThread currentThread]);
    });

//    3、下载操作
    dispatch_async(test7Queue, ^{
        NSLog(@"%@ 下载",[NSThread currentThread]);
    });
    NSLog(@"%@ 同步任务的使用",[NSThread currentThread]);
/*
 2020-03-13 10:40:52.398542+0800 filedome[52376:1051709] <NSThread: 0x600002946580>{number = 1, name = main} 用户登录
 2020-03-13 10:40:52.398749+0800 filedome[52376:1051709] <NSThread: 0x600002946580>{number = 1, name = main} 同步任务的使用
 2020-03-13 10:40:52.398776+0800 filedome[52376:1051809] <NSThread: 0x600002936d00>{number = 3, name = (null)} 查询数据
 2020-03-13 10:40:52.398841+0800 filedome[52376:1051808] <NSThread: 0x6000029290c0>{number = 4, name = (null)} 下载
 */

”用户登录”在主线程打印,后面两个在异步线程打印。“用户登录”必须放在第一位所以使用同步,无论等待多久都需要先执行完登录,后面的两个先后顺序不重要所以可以使用异步。
 开发中,在对其有依赖的必须要先执行的任务,使用同步执行。对执行的顺序无所谓的使用异步执行。

8、block 异步任务包裹同步任务

//    8、block 异步任务包裹同步任务
    dispatch_queue_t test8Queue = dispatch_queue_create("com.liujilou.queue8", DISPATCH_QUEUE_CONCURRENT);
    void (^task)() = ^{
//      1、用户登录,必须要第一个执行
        dispatch_sync(test8Queue, ^{
            NSLog(@"%@ 用户登录",[NSThread currentThread]);
        });

//        2、查询数据
        dispatch_async(test8Queue, ^{
            NSLog(@"%@ 查询数据",[NSThread currentThread]);
        });

//        3、下载
        dispatch_async(test8Queue, ^{
            NSLog(@"%@ 下载",[NSThread currentThread]);
        });
    };
    dispatch_async(test8Queue, task);
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"%@ block 异步任务包裹同步任务",[NSThread currentThread]);
/*
 2020-03-13 10:57:54.509270+0800 filedome[52659:1066304] <NSThread: 0x600000149bc0>{number = 3, name = (null)} 用户登录
 2020-03-13 10:57:54.510370+0800 filedome[52659:1066304] <NSThread: 0x600000149bc0>{number = 3, name = (null)} 下载
 2020-03-13 10:57:54.510186+0800 filedome[52659:1066302] <NSThread: 0x60000017d700>{number = 4, name = (null)} 查询数据
 2020-03-13 10:57:55.509361+0800 filedome[52659:1066171] <NSThread: 0x6000001edbc0>{number = 1, name = main} block 异步任务包裹同步任务
 */

因为整个block 是在异步执行的,所以即使里面“用户登录” 是同步执行,那也无法在主线程中执行,只能开一条异步线程执行,因为是同步的所以必须等他先执行,后面的两个“查询数据”、“下载”在同步执行完才会执行。
 */
    
9、全局队列(并发)

//    9、全局队列(并发)
    dispatch_queue_t test9Queue = dispatch_get_global_queue(0, 0);
    for (int a9=0; a9<10; a9++) {
        dispatch_async(test9Queue, ^{
            NSLog(@"%@ - %d",[NSThread currentThread],a9);
        });
    }
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"%@ 全局队列",[NSThread currentThread]);
/*
 2020-03-13 11:04:22.826343+0800 filedome[52760:1071918] <NSThread: 0x600001323c40>{number = 8, name = (null)} - 1
 2020-03-13 11:04:22.826307+0800 filedome[52760:1071599] <NSThread: 0x600001331240>{number = 7, name = (null)} - 0
 2020-03-13 11:04:22.826398+0800 filedome[52760:1071919] <NSThread: 0x600001323bc0>{number = 9, name = (null)} - 2
 2020-03-13 11:04:22.826399+0800 filedome[52760:1071920] <NSThread: 0x600001330600>{number = 10, name = (null)} - 3
 2020-03-13 11:04:22.826494+0800 filedome[52760:1071599] <NSThread: 0x600001331240>{number = 7, name = (null)} - 5
 2020-03-13 11:04:22.826494+0800 filedome[52760:1071918] <NSThread: 0x600001323c40>{number = 8, name = (null)} - 4
 2020-03-13 11:04:22.826518+0800 filedome[52760:1071920] <NSThread: 0x600001330600>{number = 10, name = (null)} - 6
 2020-03-13 11:04:22.826543+0800 filedome[52760:1071919] <NSThread: 0x600001323bc0>{number = 9, name = (null)} - 7
 2020-03-13 11:04:22.826602+0800 filedome[52760:1071599] <NSThread: 0x600001331240>{number = 7, name = (null)} - 8
 2020-03-13 11:04:22.826675+0800 filedome[52760:1071918] <NSThread: 0x600001323c40>{number = 8, name = (null)} - 9
 2020-03-13 11:04:23.826701+0800 filedome[52760:1071423] <NSThread: 0x600001349ec0>{number = 1, name = main} 全局队列
 参数:“调度优先级“ 和 “服务质量”
 */

参数:“调度优先级“ 和 “服务质量”

 小结:
 开不开线程:同步不开,异步开
 开几条线程:串行开一条,并发开多条(异步)
 主队列:专门用来在主线程上调度任务的”队列“,主队列不能再其他线程中调度任务!  同步 + 主队列 就会 死锁
 如果主线程上当前正在有执行的任务,主队列暂时不会调度任务的执行!主队列同步任务,会造成死锁。
 同步任务可以队列调度多个异步任务前,指定一个同步任务,让所有的异步任务,等待同步任务执行完成,这是依赖关系。
 全局队列:并发,能够调度多个线程,执行效率高,但是相对费点。串行队列效率低,省电省流量。或者是任务之间需要依赖也可以使用串行队列。
 

二、一些常用方法介绍

 1、GCD 的一次性代码
 整个程序运行过程中只会执行一次 + 本身是线程安全的
 应用:单例模式
 内部原理:通过判断 onceToken 的值来决定是否执行block 中的任务,只有在第一次为0,其他都为-1
 

+(instancetype)sharedInstance{
    
    static MBDB *_instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (_instance == nil) {
            _instance = [[MBDB alloc] init];
        }
    });
    return _instance;
}


 2、GCD的延迟执行
 @param when#> 设置时间(CGD中的时间单位为纳秒) description#>
 @param queue#> 队列(决定block块红的任务在哪个线程中执行,如果主队列就在主线程,否则在子线程) description#>
 @param void 执行的任务
 原理:先等2秒,然后在吧任务提交到队列,如果先提交到队列,那么任务不好控制,并且很好队列的资源

    NSLog(@"gcd 1");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (ino64_t)(2.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"gcd 2");
    });


 3、GCD的快速迭代:
 将一个文件夹下多张图片剪切到另外一个文件夹下

  // 快速迭代(遍历)
    for (int i=0; i<10; i++) {
        NSLog(@"%@ - %d",[NSThread currentThread],i);
    }

快速迭代 -- 会开启多条子线程和主线程一起并发的执行任务
 iteration  遍历的次数
 queue 队列 -- 如果是主队列则会死锁,如果是串行队列则跟for 循环一样
 size_t 索引

    dispatch_queue_t queue3 = dispatch_get_global_queue(0, 0);
    dispatch_apply(10, queue3, ^(size_t i) {
        NSLog(@"%@ - %zd",[NSThread currentThread],i);
    });
/*
 2020-03-13 14:29:20.838552+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 0
 2020-03-13 14:29:20.838714+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 1
 2020-03-13 14:29:20.838850+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 2
 2020-03-13 14:29:20.838951+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 3
 2020-03-13 14:29:20.839059+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 4
 2020-03-13 14:29:20.839612+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 5
 2020-03-13 14:29:20.840336+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 6
 2020-03-13 14:29:20.840730+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 7
 2020-03-13 14:29:20.841434+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 8
 2020-03-13 14:29:20.842253+0800 filedome[54563:1138716] <NSThread: 0x6000035527c0>{number = 1, name = main} - 9
 */


 4、GCD栅栏函数
 需求:有一个新任务打印 00000 的任务,要求在 1 2 执行完成之后执行,要保证该任务执行完成之后才能执行后面的3、4任务。
 讲解:栅栏前面的任务并发执行,后面的任务也是并发执行,当前面的任务执行完之后执行栅栏函数中的任务,等该任务执行完毕后在执行后面的任务。
 警告:不能使用全局并发队列 (dispatch_get_global_queue(0, 0)),否者不能拦截
 

 //1、创建队列
    dispatch_queue_t queue4 = dispatch_queue_create("com.liujilou.queue2-4", DISPATCH_QUEUE_CONCURRENT);
    //2、封装任务,并且添加到队列
    dispatch_async(queue4, ^{
        NSLog(@"%@ - 打印1",[NSThread currentThread]);
    });
    dispatch_async(queue4, ^{
        NSLog(@"%@ - 打印2",[NSThread currentThread]);
    });
    //栅栏函数
    dispatch_barrier_async(queue4, ^{
        NSLog(@"00000");
    });
    dispatch_async(queue4, ^{
        NSLog(@"%@ - 打印3",[NSThread currentThread]);
    });
    dispatch_async(queue4, ^{
        NSLog(@"%@ - 打印4",[NSThread currentThread]);
    });
/*
 2020-03-13 14:40:37.109550+0800 filedome[54765:1144990] <NSThread: 0x60000392da80>{number = 3, name = (null)} - 打印1
 2020-03-13 14:40:37.109550+0800 filedome[54765:1145144] <NSThread: 0x600003928340>{number = 4, name = (null)} - 打印2
 2020-03-13 14:40:37.109760+0800 filedome[54765:1145144] 00000
 2020-03-13 14:40:37.109933+0800 filedome[54765:1144990] <NSThread: 0x60000392da80>{number = 3, name = (null)} - 打印3
 2020-03-13 14:40:37.109999+0800 filedome[54765:1145144] <NSThread: 0x600003928340>{number = 4, name = (null)} - 打印4
 因为是异步执行,所以1、2是无序的  3、4是无序的,但是3、4的打印必须在栅栏之后执行。
 */


 5、GCD的队列组
 控制多条队列中任务都完成在执行新的,可用在下载多张小图片,然后都下载完成后拼接起来
 需求:有5个任务,在多个队列的子线程中并发执行,添加 打印00000 的任务,必须在所有任务完成之后再执行
 增加需求:拦截多个队列中的任务

dispatch_group_t group5 = dispatch_group_create();
    dispatch_queue_t queue51 = dispatch_queue_create("com.liujilou.queue2-5-1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue52 = dispatch_queue_create("com.liujilou.queue2-5-2", DISPATCH_QUEUE_CONCURRENT);

    dispatch_group_async(group5, queue51, ^{
        NSLog(@"%@ - 打印1",[NSThread currentThread]);
    });
    dispatch_group_async(group5, queue51, ^{
        NSLog(@"%@ - 打印2",[NSThread currentThread]);
    });
    dispatch_group_async(group5, queue51, ^{
        NSLog(@"%@ - 打印3",[NSThread currentThread]);
    });
    dispatch_group_async(group5, queue52, ^{
        NSLog(@"%@ - 打印4",[NSThread currentThread]);
    });
    dispatch_group_async(group5, queue52, ^{
        NSLog(@"%@ - 打印5",[NSThread currentThread]);
    });
//    拦截通知,当所有的任务都执行完毕然后打印 00000
//    注意:通知中的第二个参数能控制执行最后的任务在子线程 还是主线程
    dispatch_group_notify(group5, dispatch_get_main_queue(), ^{
        NSLog(@"%@ - 00000",[NSThread currentThread]);
    });
/*
 2020-03-13 15:23:05.146160+0800 filedome[55364:1166080] <NSThread: 0x600000bb1040>{number = 4, name = (null)} - 打印3
 2020-03-13 15:23:05.146176+0800 filedome[55364:1166079] <NSThread: 0x600000bb0f40>{number = 6, name = (null)} - 打印4
 2020-03-13 15:23:05.146231+0800 filedome[55364:1166077] <NSThread: 0x600000bb42c0>{number = 5, name = (null)} - 打印2
 2020-03-13 15:23:05.146496+0800 filedome[55364:1166080] <NSThread: 0x600000bb1040>{number = 4, name = (null)} - 打印5
 2020-03-13 15:23:05.147005+0800 filedome[55364:1166078] <NSThread: 0x600000bb1080>{number = 3, name = (null)} - 打印1
 2020-03-13 15:23:05.165266+0800 filedome[55364:1166018] <NSThread: 0x600000bc2880>{number = 1, name = main} - 00000
 无论是使用一个队列还是多个队列去执行,只要是在一个队列组里都是需要等全部执行完才回去通知主线程(当然也可以用一个子线程)。
 */
发布了83 篇原创文章 · 获赞 12 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/shengdaVolleyball/article/details/104844448