iOS-GCD

434 阅读4分钟

1、GCD概念

GCD特点优势

  • 全称Grand Central Dispatch
  • 纯C语言,函数强大
  • 苹果为多核并行运算提出的解决方案
  • 会利用更多的CPU内核(比如双核、四核)
  • 自动管理线程生命周期(创建线程、调度任务、销毁线程),程序员只需告诉GCD执行什么任务,不需编写任何线程管理代码

2、同步、异步

  • 任务使用block封装
  • 任务的block没有参数也没有返回值
  • 同步函数和异步函数都是耗时任务,执行起来会产生时间差
  • 可以方便理解为当前任务的单线程和多线程

2.1、同步函数:dispatch_sync

  • 必须等当前语句执行完毕,才能执行下一条语句
  • 不会开启线程
  • 在当前执行block任务

2.2、异步函数:dispatch_async

  • 不用等待当前语句执行完毕,就可以执行下一条语句
  • 会开启线程执行block的任务
  • 异步是多线程的代名词
dispatch_queue_t queue = dispatch_queue_create("LZ",DISPATCH_QUEUE_CONCRRENT);
NSLog(@"1");
dispatch_async(queue, ^{      //同步、异步函数都是耗时操作,所以先打印5(针对新创建,未被其他因素影响的线程)
    NSLog(@"2");
    dispatch_sync(queue, ^{
        NSLog(@"3");
    });                       //同步函数阻塞后续内容执行,所以先3后4
    NSLog(@"4");
});
NSLog(@"5");

执行结果:15234

2.3、死锁(_dispatch_sync_f_slow)

将上边的代码从并发改为串行

//为方便讲解,我们把代码标上序号
1.    dispatch_queue_t queue = dispatch_queue_create("LZ",DISPATCH_QUEUE_SERIAL);
2.    NSLog(@"1");
3.    dispatch_async(queue, ^{      //同步、异步函数都是耗时操作
4.        NSLog(@"2");
5.        dispatch_sync(queue, ^{   //队列顺序:1、5、2、同步block块、4、3,但是同步block护犊子强制3先执行,在同步函数不能开辟新线程的情况下,遵循FIFO,3只能在4后执行,3无法插队造成死锁
6.            NSLog(@"3");
7.        });                       
8.        NSLog(@"4");
9.    }); <---           //上面同步函数阻塞的不止是4的打印,还有异步函数的完成,异步函数内部已结束,要先执行完成,再执行3,但被同步函数阻塞,因此将4的打印屏蔽也会死锁
10.   NSLog(@"5");

执行结果:152 崩溃

image.png

2.4、死锁修改

1.    dispatch_queue_t queue = dispatch_queue_create("LZ",DISPATCH_QUEUE_SERIAL);
2.    NSLog(@"1");
3.    dispatch_async(queue, ^{      
4.        NSLog(@"2");
          //从 sync 改为 async
5.        dispatch_async(queue, ^{              
6.            NSLog(@"3");
7.        });  
          //睡眠2秒
8.        sleep(2);
9.        NSLog(@"4");
10.   }); 
11.   NSLog(@"5");

执行结果:152 (等待2秒) 43
  • 3 并没有比 4 先打印,因为 异步只是任务从队列中取出来后放到另一个线程里执行,第9行任务从队列里出来前睡了2秒,按照串行队列规则,第9行任务不从队列中出去第5-7行任务从队列里出不来,线程拿不到任务执行

  • 那么将 NSLog(@"4") 也放入异步执行的结果呢?

    1.    dispatch_queue_t queue = dispatch_queue_create("LZ",DISPATCH_QUEUE_SERIAL);
    2.    NSLog(@"1");
    3.    dispatch_async(queue, ^{      
    4.        NSLog(@"2");
              // 都改为 async
    5.        dispatch_async(queue, ^{   
    6.            sleep(2);
    7.            NSLog(@"3");
    8.        });  
    9.        dispatch_async(queue, ^{
    10.           NSLog(@"4");
    11.       });
    12.   }); 
    13.   NSLog(@"5");
    
    执行结果:152 (等待2秒) 34
    
    • 4 的打印要等待 3 睡眠2秒后才执行
    • 虽然打印 3 和 4 都是异步执行的,但因为串行队列只能开启一条线程他们异步后的执行线程是同一条,因此表面上看虽然都是异步了,但 4 还是在 3 后执行

补充

  • 异步和多线程并不是同等关系,异步是目的,多线程是异步的一种手段。异步是当一个调用者,调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其他事情的能力,实现异步可以采用多线程技术或者交给其他进程来处理(APP间进程独立、MAC系统不独立,文中主要讨论APP,故将异步与多线程近似对待)等

2、队列

  • 队列是一种调度,将任务排序并进入runnable状态,具体执行还是在线程中,需要线程池

  • 串行队列:数据结构:FIFO(先IN先OUT),先调度先执行

  • 并发队列(Concurrent):同一时间可以调度多个任务,先调度不一定先执行

2.1、并发与并行

  • 并行(Parallel):两个或两个以上事件或活动在同一时刻发生,线程可在不同CPU上同时执行(hadoop集群),多个线程互相不抢夺资源,可同时进行
  • 并行和并发:并行是彻底的同一时间执行多任务,并发是将任务打散,微观上以很快的速度交替运行,宏观上看实现了同时执行
  • 异步是多个任务并行的前提条件

image.png

2.2、函数与队列

  • 同步、异步: 指能否开启新线程的能力
  • 串行、并发: 指是否需要等待上一个任务完成才能调度下一个任务
注意
  • 异步与并发: 虽然异步和并发都是说不用等这个任务执行完,就可以执行后边的任务,但是异步是说将这个任务放到别的线程执行,而并发指的是还是这个线程,只是让后边任务先执行

  • 类比:

    • 异步:去另一台提款机取钱
    • 并发:前边的忘密码了后边先取 image.png
  • 同步 + 串行队列不能开启新线程;在一条线程中,完成一个任务后才能从队列中拿一个新任务执行

  • 同步 + 并发队列不能开启新线程;在一条线程中,如果前边任务停滞则从队列中拿下一个任务执行(可能一口气从队列中拿好几个任务执行),执行哪个看前置任务停滞后的系统调度

  • 异步 + 串行队列:虽然能开启新线程,但 只能开启一条线程线程完成一个任务后才能从队列中拿一个新任务执行

  • 异步 + 并发队列:能开启新线程,而且 能开一堆线程线程们每条都有一下拿好几个任务执行的能力,执行哪个看前置任务停滞后的系统调度

小测试1
1.    dispatch_queue_t queue = dispatch_queue_create("LZ",DISPATCH_QUEUE_SERIAL);
2.    dispatch_queue_t queue2 = dispatch_queue_create("LZ2",DISPATCH_QUEUE_SERIAL);
3.    dispatch_queue_t queue3 = dispatch_queue_create("LZ3",DISPATCH_QUEUE_CONCURRENT);
4.
5.    NSLog(@"1");
6.    dispatch_async(queue, ^{     //可尝试改为queue3
7.        sleep(2);
8.        NSLog(@"2");
9.        dispatch_async(queue, ^{ //可尝试改为queue3
10.           NSLog(@"3");
11.       });
12.       NSLog(@"4");
13.   });
14.   NSLog(@"5");
15.
16.   dispatch_async(queue, ^{     //可尝试改为queue3
17.       NSLog(@"6");
18.   });

执行结果:15 (睡眠2秒) 2463
  • 同步、异步向队列中添加任务都是将代码块(7-12行、17行、10行)添加到队列中,代码块中内容先不用理会,只有线程执行到时才开始考虑,因此队列中任务的顺序是 5行 --> 14行 --> 7-12行 --> 17行 --> 8行 --> 10行,本例中因为放到了 串行队列 中,因此17行虽然是异步,也要等7行睡眠2秒后才能执行

  • 如果将 串行队列queue 换成 并发队列queue3,则执行结果:156 (睡眠2秒) 243,因为 并发队列前置任务停滞能直接取下一个任务执行,7-12行任务睡眠了就调度17行任务先执行

小测试2
1.    dispatch_queue_t queue = dispatch_queue_create("LZ",DISPATCH_QUEUE_SERIAL);
2.    dispatch_queue_t queue2 = dispatch_queue_create("LZ2",DISPATCH_QUEUE_SERIAL);
3.    dispatch_queue_t queue3 = dispatch_queue_create("LZ3",DISPATCH_QUEUE_CONCURRENT);
4.
5.    NSLog(@"1");
6.    dispatch_async(queue, ^{     
7.        NSLog(@"2");
8.        dispatch_async(queue, ^{ 
9.            NSLog(@"3");
10.       });
11.       NSLog(@"4");
12.   });
13.   NSLog(@"5");
14.
      //改为queue2
15.   dispatch_async(queue2, ^{     
16.       NSLog(@"6");
17.   });

执行结果:15 (6?) 2 (6?) 4 (6?) 3 (6?)
  • 最后的16行任务与其他任务 被添加到了不同的队列中,所以说与它们根本就没有什么关系,因此 数字6 的打印可能在打印数字2、4、3时的任何一个时机执行

2.3、主队列与全局队列

2.3.1、主队列dispatch_get_main_queue()
  • 专门用来在主线程上调度任务的串行队列,不会开启线程
  • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
2.3.2、全局并发队列dispatch_get_global_queue(0,0)
  • 全局队列是一个并发队列
  • 在使用多线程时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列

3、GCD的应用

  1. 单例
  2. 同步/异步
  3. 延迟
  4. dispatchSource
  5. 调度组
  6. 线程通讯
  7. 栅栏函数
  8. 信号量/锁

阻塞方式

栅栏函数
  • 作用:控制任务执行顺序,同步
  • dispatch_barrier_async:前边的任务执行完毕后才会来到这里
  • dispatch_barrier_sync :作用相同,但是会阻塞线程,影响后边的任务执行
  • 只能控制同一并发队列
调度组
  • dispatch_gruop_create:创建组
  • dispatch_group_async :进组任务
  • dispatch_group_notify :进组任务执行完毕通知
  • dispatch_group_wait :进组任务等待
  • dispatch_group_enter :进组
  • dispatch_group_leave :出组
信号量dispatch_semaphore_t
  • dispatch_semaphore_create:创建信号量
  • dispatch_semaphore_wait : 信号量等待
  • dispatch_semaphore_signal :信号量释放
  • 同步-->当锁,控制GCD最大并发数
Dispatch_Source
  • 其CPU负荷非常小,尽量不占用资源
  • 联结的优势
  • 在任一线程上调用它的一个函数dispatch_source_merge_data后,会执行Dispatch Source事先定义好的句柄(可以把这个句柄简单理解为一个block)这个过程叫Custom event,用户事件。是Dispatch Source支持处理的一种事件
    • 句柄是一种指向指针的指针,它指向的就是一个类或者结构,它和系统有很密切的关系HINSTANCE(实例句柄),HBITMAP(位图句柄),HDC(设备表述句柄),HICON(图标句柄)等。这当中还有一个通用句柄,就是HANDLE
  • dispatch_source_create: 创建源
  • dispatch_source_set_event_handler:设置源事件回调
  • dispatch_source_merge_data: 源事件设置数据
  • dispatch_source_get_data: 获取源事件数据
  • dispatch_resume: 继续
  • dispatch_suspend: 挂起