iOS-28.GCD的函数与队列

28 阅读8分钟

ios底层文章汇总

GCD简介

GCD 全称 Grand Central Dispatch,基于C语言实现的多线程机制,将任务添加到队列,并指定执行任务的函数。

是Apple提供的一个多核编程的解决方案。它允许将一个程序切分为多个单一任务,然后提交到工作队列中并发或串行地执行。

GCD优势

  • GCD 可用于多核的并行运算,会自动合理地利用CPU内核(比如双核、四核)。
  • GCD 使用简单,开发者要做的只是定义执行的 任务,追加到适当的 队列 中,并且指定执行任务的 函数。配合Block,使用起来也更加方便灵活。
  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程),程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。

image.png

任务

任务就是要执行的操作,在GCD中也就是在Block内的代码段,任务的Block没有参数也没有返回值。

函数

函数决定了任务的执行方式。同步执行 还是 异步执行

两者的主要区别是:是否需要等待当前任务的返回结果,以及是否具备开启新线程的能力

  • 同步函数(sync)

    • 同步函数执行的任务,调用一旦开始,调用者必须等待任务执行完毕,才能继续执行后续行为。
    • 不具备开启新线程的能力(任务只能在当前线程中执行任务)。
  • 异步函数(async)

    • 异步函数执行的任务,调用者无需等待任务执行完毕,就可以继续执行后续行为。
    • 具备开启新线程的能力(可以在新的线程中执行任务)。

异步执行虽然具有开启新线程的能力,但并不一定要开启新线程,还与任务所属队列有关,eg:异步执行主队列中的任务就不会开启新线程。

同步执行的任务记为同步任务 dispatch_sync 异步执行的任务记为异步任务 dispatch_async

队列

是一种特殊的线性表,基本特性就是 FIFO(先进先出)

在GCD中,需要将任务添加到队列中,新任务被插入到队列的末尾,而调度任务的时候总是从队列的头部开始执行。每调度一个任务,则从队列中移除该任务。

在GCD中有两种队列:串行队列 和 并发队列。两者都遵循FIFO原则,两者的主要区别是:执行顺序不同使用的线程个数不同

image.png

  • 串行队列每次只有一个任务被调度,任务一个接一个地执行,且任务都在同一个线程执行。只有前一个任务被调度执行完毕才能调度下一个任务

  • 并发队列可以让多个任务并发(”同时“)执行。这取决于有多少可以利用的线程,假设在两条线程可用的情况下,那么任务1和任务2可分别在不同线程中并发执行

函数与队列

  • 同步函数串行队列
    • 不会开启线程,在当前线程执行任务
    • 任务串行执行,任务一个接一个执行
    • 会产生堵塞

//  同步+串行 任务
- (void)sync_serial{

   // 打印当前线程
   NSLog(@"currentThread---%@",[NSThread currentThread]);  
   NSLog(@"begin");
   
   // 串行队列
   dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_SERIAL);
   
   // 任务 1
   dispatch_sync(queue, ^{
       sleep(1);                                       // 模拟耗时操作
       NSLog(@"任务1---%@",[NSThread currentThread]);   // 打印当前线程
   });
   // 任务 2
   dispatch_sync(queue, ^{
       sleep(1);                                       // 模拟耗时操作
       NSLog(@"任务2---%@",[NSThread currentThread]);  // 打印当前线程
   });
   // 任务 3
   dispatch_sync(queue, ^{
       sleep(1);                                       // 模拟耗时操作
       NSLog(@"任务3---%@",[NSThread currentThread]);  // 打印当前线程
   });
   
   NSLog(@"end");
}


  • 同步函数并行队列
    • 不会开启线程,在当前线程执行任务
    • 任务一个接一个执行
  // 同步+并发 任务
- (void)sync_concurrent{
    
    // 打印当前线程
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"begin");
    
    // 并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 任务 1
    dispatch_sync(queue, ^{
        sleep(1);                                       // 模拟耗时操作
        NSLog(@"任务 1---%@",[NSThread currentThread]);  // 打印当前线程
    });
    // 任务 2
    dispatch_sync(queue, ^{
        sleep(1);                                       // 模拟耗时操作
        NSLog(@"任务 2---%@",[NSThread currentThread]);  // 打印当前线程
    });
    // 任务 3
    dispatch_sync(queue, ^{
        sleep(1);                                       // 模拟耗时操作
        NSLog(@"任务 3---%@",[NSThread currentThread]);  // 打印当前线程
    });
    
    NSLog(@"end");
}

任务的依赖的是线程而非队列,虽然并发队列支持多任务同时执行,但同步并不具备开启线程的能力,只能利用当前线程,当前线程只有一个(主线程),所以任务还是依次在主线程中执行

  • 异步函数串行队列
    • 开启一条新线程线程,串行队列的任务都在同一条线程执行
    • 任务一个接一个执行
// 异步+串行 任务
- (void)async_serial{
    
    // 打印当前线程
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"begin");
    
    // 串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_SERIAL);
    
    // 任务 1
    dispatch_async(queue, ^{
        sleep(1);                                       // 模拟耗时操作
        NSLog(@"任务 1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    // 任务 2
    dispatch_async(queue, ^{
        sleep(1);                                       // 模拟耗时操作
        NSLog(@"任务 2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    // 任务 3
    dispatch_async(queue, ^{
        sleep(1);                                       // 模拟耗时操作
        NSLog(@"任务 3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"end");
}



  • 异步函数并行队列
    • 可开启线程多个线程,同时执行多个任务
    • 任务异步执行,没有顺序,CPU调度有关
// 异步+并发 任务
- (void)async_concurrent{
    
    // 打印当前线程
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"begin");
    
    // 并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 任务 1
    dispatch_async(queue, ^{
        sleep(1);                                       // 模拟耗时操作
        NSLog(@"任务 1---%@",[NSThread currentThread]);  // 打印当前线程
    });
    // 任务 2
    dispatch_async(queue, ^{
        sleep(1);                                       // 模拟耗时操作
        NSLog(@"任务 2---%@",[NSThread currentThread]);  // 打印当前线程
    });
    // 任务 3
    dispatch_async(queue, ^{
        sleep(1);                                       // 模拟耗时操作
        NSLog(@"任务 3---%@",[NSThread currentThread]);  // 打印当前线程
    });
    
    NSLog(@"end");
}

- 同步+主队列

```objc
// 同步+主队列 任务
- (void)sync_main{
    
    // 打印当前线程
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"begin");
    
    // 主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 任务 1
    dispatch_sync(queue, ^{
        sleep(1);                                       // 模拟耗时操作
        NSLog(@"任务 1---%@",[NSThread currentThread]);  // 打印当前线程
    });
    // 任务 2
    dispatch_sync(queue, ^{
        sleep(1);                                       // 模拟耗时操作
        NSLog(@"任务 2---%@",[NSThread currentThread]);  // 打印当前线程
    });
    // 任务 3
    dispatch_sync(queue, ^{
        sleep(1);                                       // 模拟耗时操作
        NSLog(@"任务 3---%@",[NSThread currentThread]);  // 打印当前线程
    });
    
    NSLog(@"end");
}

当运行至begin之后,程序崩溃退出了.

当前线程为主线程,在主线程中执行 sync_main ,相当于将sync_main 加入主队列中,当追加任务1的时候,再将任务1加入主队列,由于主队列是串行队列,任务1 就需等待sync_main 执行完毕,而又因是同步执行,sync_main 需等待任务1执行完毕 ,这样的互相等待,就导致了死锁的发生

  • 异步+主队列(dispatch_get_main_queue())

    • 主队列是专门用来在主线程上调度的串行队列

    • 主队列不会开启线程,如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度

    • 所有的任务都是在主线程中执行的(虽然异步执行具备开启线程的能力,但因为是主队列,所以所有的任务都在主线程中)。

    • 任务是按顺序执行(主队列是串行队列,每次只执行一个任务,任务一个接一个执行)

// 异步+主队列 任务
- (void)async_main{
    
    // 打印当前线程
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"begin");
    
    // 主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 任务 1
    dispatch_async(queue, ^{
        sleep(1);                                       // 模拟耗时操作
        NSLog(@"任务 1---%@",[NSThread currentThread]);  // 打印当前线程
    });
    // 任务 2
    dispatch_async(queue, ^{
        sleep(1);                                       // 模拟耗时操作
        NSLog(@"任务 2---%@",[NSThread currentThread]);  // 打印当前线程
    });
    // 任务 3
    dispatch_async(queue, ^{
        sleep(1);                                       // 模拟耗时操作
        NSLog(@"任务 3---%@",[NSThread currentThread]);  // 打印当前线程
    });
    
    NSLog(@"end");
}
  • 全局队列

    • 为了方便使用,苹果提供了全局队列dispatch_get_global_queue(0,0),是一个并发队列
    • 在使用多线程开发时,如果对队列没有特殊要求,在执行异步任务时,可以直接使用全局队列。

image.png

并发队列的并发功能只有在 异步函数下才有效。 并发并行的区别了,并行是多核下真正的同时执行,而并发则是由CPU时间片的轮转机制

【面试题】

- (void)textDemo{
    
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    // 耗时
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });

    NSLog(@"5");

    // 15243
}

- (void)textDemo{
    
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    // 耗时
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    sleep(2);
    NSLog(@"5");
    // 12435
}


- (void)textDemo2{
    // 同步队列
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    // 异步函数
    dispatch_async(queue, ^{
        NSLog(@"2");
        // 同步
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
         NSLog(@"4");
    });
    NSLog(@"5");
    
    // 1 5 2 3 4
}



- (void)textDemo2{
    // 同步队列
    dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
    NSLog(@"1");
    // 异步函数
    dispatch_async(queue, ^{
        NSLog(@"2");
        // 同步
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        // NSLog(@"4");
    });
    NSLog(@"5");
    
    // 1 5 2 死锁

}

image.png

image.png

GCD 底层原理

队列的创建

符号断点 dispatch_queue_create,定位底层库libdispatch.dylib image.png

  • dispatch_get_main_queue()解析:

搜索com.apple.main-thread

队列总共有两种: 并发队列 和 串行队列

  • 串行队列:serialQueue、mainQueue
  • 并发队列:concurrentQueue、globalQueue

image.png

初始化和下层的绑定 libdispatch_init image.png

  • dispatch_get_global_queue(0,0)解析 搜索com.apple.root.default-qos 可查看到定义在struct dispatch_queue_global_s _dispatch_root_queues[]

image.png

#define _DISPATCH_GLOBAL_ROOT_QUEUE_ENTRY(n, flags, ...) \
[_DISPATCH_ROOT_QUEUE_IDX(n, flags)] = { \
DISPATCH_GLOBAL_OBJECT_HEADER(queue_global), \
.dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE, \
.do_ctxt = _dispatch_root_queue_ctxt(_DISPATCH_ROOT_QUEUE_IDX(n, flags)), \
.dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL), \
.dq_priority = flags | ((flags & DISPATCH_PRIORITY_FLAG_FALLBACK) ? \
_dispatch_priority_make_fallback(DISPATCH_QOS_##n) : \
_dispatch_priority_make(DISPATCH_QOS_##n, 0)), \
__VA_ARGS__ \
}