今天看啥  ›  专栏  ›  末日铁拳

iOS多线程(二)

末日铁拳  · 掘金  ·  · 2019-09-03 15:37
阅读 5

iOS多线程(二)

一、GCD简介

  • 什么是GCD?

    全称是 Grand Central Dispatch

    纯 C 语言,提供了非常多强大的函数

  • GCD的优势

    GCD 是苹果公司为多核的并行运算提出的解决方案

    GCD 会自动利用更多的CPU内核(比如双核、四核)

    GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

    程序员只需要告诉GCD 想要执行什么任务,不需要编写任何线程管理代码

  • 劣势

    自动控制生命周期,程序员不能自由操作

  • 函数

    - (void)syncTest
    {
        // 把任务添加到队列 --> 函数
        // 任务   ( _t ref 都是 c对象)
        dispatch_block_t block = ^{
            NSLog(@"hello GCD");
        };
        //串行队列 <- 因为第二个参数是NULL
        dispatch_queue_t queue = dispatch_queue_create("com.hehe.cn", NULL);
        // 函数
        dispatch_async(queue, block);
    }
    复制代码
    • 任务使用 block 封装
    • 任务的 block 没有参数也没有返回值
    • 执行任务的函数
      • 异步 dispatch_async
        • 不用等待当前语句执行完毕,就可以执行下一条语句
        • 会开启线程执行 block的任务
        • 异步是多线程的代名词
      • 同步 dispatch_sync
        • 必须等待当前语句执行完毕,才会执行下一条语句
        • 不会开启线程
        • 在当前执行 block的任务
  • 队列

    串行队列 - DISPATCH_QUEUE_SERIAL: 只能一个一个任务挨个出去

    并行队列 - DISPATCH_QUEUE_CONCURRENT: 多个任务同时出去

  • 队列和函数的组合

    • 同步函数串行队列:

      1、不会开启线程,在当前线程执行任务

      2、任务串行执行,任务一个接着一个

      3、会产生堵塞

    • 同步函数并发队列:

      1、不会开启线程,在当前线程执行任务

      2、任务一个接着一个

    • 异步函数串行队列:

      1、开启线程一条新线程

      2、任务一个接着一个

    • 异步函数并发队列:

      1、开启线程,在当前线程执行任务

      2、任务异步执行,没有顺序,CPU调度有关

    - (void)testDemo
    {
        dispatch_queue_t queue = dispatch_queue_create("hehe",DISPATCH_QUEUE_CONCURRENT);
        NSLog(@"1");
        // 耗时
        dispatch_async(queue, ^{
            NSLog(@"2");
            dispatch_async(queue, ^{
                NSLog(@"3");
            });
            NSLog(@"4");
        });
        NSLog(@"5");
        // 打印: 1 5 2 4 3 
        // 因为dispatch_async不堵塞外部线程
        // 假设:一个通道代表串行,多个通道代表并发,一辆车代表一个线程,每个通道有每辆车的出货的顺序
        // 个人理解:
        //        车a:拉1,拉异步代码块:“我有车你先走”,拉5,其中异步代码块产生车b
        //        车b:拉2,拉异步代码块:“我有车你先走”,拉4,其中异步代码块产生车c
        //        车c:拉3
        //
        //
        //
    }
    复制代码
    - (void)testDemo2
    {
        // 串行队列
        dispatch_queue_t queue = dispatch_queue_create("hehe",DISPATCH_QUEUE_SERIAL);
        NSLog(@"1");
        // 异步函数
        dispatch_async(queue, ^{
            NSLog(@"2");
            // 同步
            dispatch_sync(queue, ^{
               //NSLog(@"3");
            });
            // 这个地方没有代码一样会产生死锁
            //NSLog(@"4");
        });
        NSLog(@"5");
        // 152
        // 个人理解:
        //         串行队列说明只有一条通道,异步函数会创建多辆车,通道有每辆车的出货的顺序
        //         以下是计划:
        //            1、车a拉1,车a拉异步代码块,异步代码块说:“你先走,我自己有车”,车a拉了5走了
        //            2、异步代码块中创建车b
        //            3、车b拉2,车b拉同步代码块,车b拉4
        //         实际情况:
        //            车b在拉同步代码块的时候被告知:“我没车,你得拉3,不然你啥也别干了”
        //            车b:“通道要求我得先把4拉了”
        //         于是:
        //            产生死锁,3和4谁也不拉
    }
    复制代码
    - (void)testDemo1
    {
        dispatch_queue_t queue = dispatch_queue_create("hehe",DISPATCH_QUEUE_CONCURRENT);
        NSLog(@"1");
        dispatch_async(queue, ^{
            NSLog(@"2");
            dispatch_sync(queue, ^{
                NSLog(@"3");
            });
            NSLog(@"4");
        });
        NSLog(@"5");
      
        //打印 :1 5 2 3 4
      
        // 个人理解:
        //         并行队列说明有多条通道,同步函数会创建一辆车,每个通道有出货的顺序
        //         以下是计划:
        //            1、车a拉1,车a拉异步代码块,异步代码块说:“你先走,我自己有车”,车a拉了5走了
        //            2、异步代码块中创建车b
        //            3、车b拉2,车b拉同步代码块,车b拉4
        //            4、同步代码块说:“你得先拉3,不然你啥也别干了”
        //            5、车b说:“当前通道要求我先拉4,我换个通道把3拉了再回来拉4”
    }
    复制代码
  • 死锁

    • 主线程因为你同步函数的原因等着先执行任务
    • 主队列等着主线程的任务执行完毕再执行自己的任务
    • 主队列和主线程相互等待会造成死锁
  • 特殊队列

    系统加载时候的默认队列:

    1. dispatch_get_global_queue(0, 0);

      全局队列:并发队列

      不建议用

      • 为了方便程序员的使用,苹果提供了全局队列
      • 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列
    2. dispatch_get_main_queue();

      主队列:串行队列

      • 专门用来在主线程上调度任务的队列
      • 不会开启线程
      • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
        int a = 0; 
        while (a<10) 
        { 
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                a++;
            });
        }
    
    //问题1:
    //     a会报错,为什么?
    //     
    //问题2:
    //     a会输出什么值?
    //     >=10
    //     原因:资源抢夺
    //          创建很多条线程,都会操作a
    //问题3:  
    //     输出a最后的值该怎么修改?  
    //    
    
        __block int a = 0; //问题1修复
    		//把a从栈区copy成一个struct(中有a的指针地址和a的值)
    		//block中会修改newA
        while (a<10) 
        { 
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                a++;
            });
        }
    
    		//问题3
    		//最后
    		// 主线程 -- 堵塞 -- I/O -- 代码执行的耗时 -- 相当于一个同步效果
    		// 堵塞 -- 给你足够的时间 -- 把前面的任务执行完毕之后 -- 往队列加入任务 -- 再执行
        NSLog(@"主线程%d",a);//此时打印的也不是真的值
    		dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"最后%d",a);
        });
    复制代码

二、GCD的应用

  1. 栅栏函数

    • 一定要是自定义的并发队列,不然没有效果

      如果栅栏函数堵塞了全局并发队列那系统就GG了

    • dispatch_barrier_async和dispatch_barrier_sync

      • dispatch_barrier_async 前面的任务执行完毕才会来到这里,不影响队列外任务的执行
      • dispatch_barrier_sync 作用相同,但是这个会堵塞线程,影响后面的任务执行,包括队列外的任务
    • 栅栏函数只能控制同一并发队列 --- 不够优秀的地方 --- 不利于封装

  2. 调度组

    控制任务执行顺序

    • dispatch_group_create 创建组

      dispatch_group_async 进组任务

      dispatch_group_notify 进组任务执行完毕通知

      dispatch_group_wait 进组任务执行等待时间

    • dispatch_group_enter 进组

      dispatch_group_leave 出组

      注意搭配使用

  3. 信号量dispatch_semaphore_t

  4. Dispatch_Source




原文地址:访问原文地址
快照地址: 访问文章快照