iOS GCD使用

看这篇文章前,如果对队列和任务的理解比较混乱,可以参考 概念篇–队列与任务

1. GCD的使用步骤

GCD 的使用步骤只有两步:

  • 创建一个队列(串行队列或并发队列);

  • 将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)。

1.1 队列的创建方法 / 获取方法

// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("a.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("b.testQueue", DISPATCH_QUEUE_CONCURRENT);
// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();
// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
复制代码

1.2 任务的创建方法

// 同步执行任务 创建方法
dispatch_sync(queue, ^{
    // 这里放同步执行任务代码
});
// 异步执行任务 创建方法
dispatch_async(queue, ^{
    // 这里放异步执行任务代码
});
复制代码

2. 死锁的情况

2.1 在『异步执行』+『串行队列』的任务中,又嵌套了『当前的串行队列』,然后进行『同步执行』。

dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{    // 异步执行 + 串行队列
    dispatch_sync(queue, ^{  // 同步执行 + 当前串行队列
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
});
复制代码

执行上面的代码会导致 串行队列中追加的任务 和 串行队列中原有的任务 两者之间相互等待,阻塞了『串行队列』,最终造成了串行队列所在的线程(子线程)死锁问题。

2.2 主队列造成死锁的原因同上. 所以,这也进一步说明了主队列其实并不特殊。

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@" gcd 前");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@" gcd 内");
    });
    NSLog(@" gcd 后");
}
复制代码

2.3 在『同步执行』+『串行队列』的任务中,又嵌套了『当前的串行队列』,然后进行『同步执行』。

dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{    // 同步执行 + 串行队列
    dispatch_sync(queue, ^{  // 同步执行 + 当前串行队列
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
});
复制代码

3. GCD简单使用

3.1 处理耗时操作

由线程完成耗时操作,需要回到主线程,刷新UI

/**
 * 线程间通信
 */
- (void)communication {
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        // 异步追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        
        // 回到主线程
        dispatch_async(mainQueue, ^{
            // 追加在主线程中执行的任务
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
    });
}
复制代码

3.2 延迟执行任务 dispatch_after

dispatch_after 方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after 方法是很有效的。

/**
 * 延时执行方法 dispatch_after
 */
- (void)after {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"asyncMain---begin");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2.0 秒后异步追加任务代码到主队列,并开始执行
        NSLog(@"after---%@",[NSThread currentThread]);  // 打印当前线程
    });
}
复制代码

4. GCD高级应用

4.1 栅栏函数

dispatch_barrier_async的简单使用

 dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);

 dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_barrier_async(queue, ^{
        // 追加任务 barrier
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
    });
    
    dispatch_async(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });

复制代码

栅栏函数有 2 种,同步dispatch_barrier_sync和异步dispatch_barrier_async,下面来打印比较下它们的异同

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    dispatch_async(dispatch_queue_create("mmmmmm", DISPATCH_QUEUE_CONCURRENT), ^{
        [self logBarrierOrder];
    }); 
}

- (void)logBarrierOrder {
    NSLog(@"%@ >>>>>  start ", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("test.barrier.queue", DISPATCH_QUEUE_CONCURRENT);
    //异步函数 无序执行
    
    NSLog(@"%@ >>>>>  barrier 前面 A ", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"%@ >>>>> 任务  1 ", [NSThread currentThread]);
    });
    
    NSLog(@"%@ >>>>>  barrier 前面 B ", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(3);
        NSLog(@"%@ >>>>> 任务  2 ", [NSThread currentThread]);
    });
    
    NSLog(@"%@ >>>>>  barrier 前面 C ", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"%@ >>>>> 任务  3 ", [NSThread currentThread]);
    });
    
    NSLog(@"%@ >>>>>  barrier 前面 D ", [NSThread currentThread]);  
    dispatch_barrier_sync(queue, ^{
        sleep(7);
        NSLog(@"%@ ++++++  barrier ++++++ ", [NSThread currentThread]);
    });
    
    
    NSLog(@"%@ >>>>>  barrier 后面 E ", [NSThread currentThread]);
    dispatch_async(queue, ^{
        NSLog(@"%@ >>>>> 任务  4 ", [NSThread currentThread]);
    });
    
    NSLog(@"%@ >>>>>  barrier 后面 F ", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(10);
        NSLog(@"%@ >>>>> 任务  5 ", [NSThread currentThread]);
    });
    
    NSLog(@"%@ >>>>>  barrier 后面 G ", [NSThread currentThread]);
    dispatch_async(queue, ^{
        NSLog(@"%@ >>>>> 任务  6 ", [NSThread currentThread]);
    });
    
    NSLog(@"%@ >>>>>  end ", [NSThread currentThread]);
}
复制代码

dispatch_barrier_sync执行结果

>>>>> start

>>>>> barrier 前面 A

>>>>> barrier 前面 B

>>>>> barrier 前面 C

>>>>> barrier 前面 D

>>>>> 任务 3

>>>>> 任务 2

>>>>> 任务 1

++++++ barrier ++++++

>>>>> barrier 后面 E

>>>>> barrier 后面 F

>>>>> 任务 4

>>>>> barrier 后面 G

>>>>> end

>>>>> 任务 6

>>>>> 任务 5

dispatch_barrier_async执行结果

>>>>> start

>>>>> barrier 前面 A

>>>>> barrier 前面 B

>>>>> barrier 前面 C

>>>>> barrier 前面 D

>>>>> barrier 后面 E

>>>>> barrier 后面 F

>>>>> barrier 后面 G

>>>>> end

>>>>> 任务 3

>>>>> 任务 2

>>>>> 任务 1

++++++ barrier ++++++

>>>>> 任务 4

>>>>> 任务 6

>>>>> 任务 5

  • 共同点
    • 先等任务1,2,3执行完,再执行barrier,执行完barrier,最后执行任务 4,5,6
  • 不同点
    • dispatch_barrier_sync需要等待自己的任务(barrier)结束之后,才会继续执行写在barrier后面的任务(E、F、G),然后执行后面的任务(4,5,6)
    • dispatch_barrier_async将自己的任务(barrier)插入到queue之后,不会等待自己的任务结束,它会继续把后面的任务(E、F、G)插入到queue

栅栏函数注意点:

  • 尽量使用自定义的并发队列,使用全局队列起不到栅栏函数的作用.使用全局队列时由于对全局队列造成堵塞,可能致使系统其他调用全局队列的地方也堵塞从而导致崩溃(并不是只有你在使用这个队列)
  • 栅栏函数只能控制同一并发队列
  • dispatch_barrier_async和dispatch_barrier_sync的异同:都可以阻止(此线程中)队列的执行,但是dispatch_barrier_sync也阻止了线程的执行
  • dispatch_barrier_sync是在主线程中执行,dispatch_barrier_async是在子线程中执行。
  • 在串行队列中,dispatch_barrier_sync和dispatch_barrier_async功能一样。dispatch_async会创建新的线程,如果是串行队列,那么只开启一个线程, 任务按照加入的顺序执行。

4.2 一次性代码(只执行一次): dispatch_once

/**
 * 一次性代码(只执行一次)dispatch_once
 */
- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行 1 次的代码(这里面默认是线程安全的)
    });
}
复制代码

4.3 GCD 快速迭代方法:dispatch_apply

如果是在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。

我们可以利用并发队列进行异步执行。比如说遍历 0~5 这 6 个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply 可以 在多个线程中同时(异步)遍历多个数字。

还有一点,无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法。

/**
 * 快速迭代方法 dispatch_apply
 * 遍历 0~5 这 6 个数字
 */
- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    NSLog(@"apply---begin");
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
}
复制代码

因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是 apply—end 一定在最后执行。这是因为 dispatch_apply 方法会等待全部任务执行完毕。

4.4 组队列

dispatch_group_notify

监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。

/**
 * 队列组 dispatch_group_notify
 */
- (void)groupNotify {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");
    
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程

        NSLog(@"group---end");
    });
}
复制代码

dispatch_group_enter、dispatch_group_leave

  • dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1
  • dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。
  • 当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务。
/**
 * 队列组 dispatch_group_enter、dispatch_group_leave
 */
- (void)groupEnterAndLeave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程

        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步操作都执行完毕后,回到主线程.
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    
        NSLog(@"group---end");
    });
}
复制代码

dispatch_group_wait

暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。

/**
 * 队列组 dispatch_group_wait
 */
- (void)groupWait {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");
    
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"group---end");
    
}
复制代码

4.5 GCD 信号量:dispatch_semaphore

  • dispatch_semaphore相关的3个函数

    • // 创建信号量,参数:信号量的初值,如果小于0则会返回NULL
      dispatch_semaphore_t dispatch_semaphore_create(long value);

    • /* 等待降低信号量,接收一个信号和时间值(多为DISPATCH_TIME_FOREVER)
       若信号的信号量为0,则会阻塞当前线程,直到信号量大于0或者经过输入的时间值;
      若信号量大于0,则会使信号量减1并返回,程序继续住下执行
      */
      long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

    • // 提高信号量, 使信号量加1并返回
      long dispatch_semaphore_signal(dispatch_semaphore_t dsema);

  • 发送信号(signal)与等待信号(wait)往往是成对出现的。

① 通过dispatch_semaphore控制异步方法一个执行完再执行另一个
-(void)dispatchSignal{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);   
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     
    //任务1
    dispatch_async(quene, ^{
        NSLog(@"run task 1");
        sleep(2);
        NSLog(@"complete task 1");
        dispatch_semaphore_signal(semaphore);       
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    //任务2
    dispatch_async(quene, ^{
        NSLog(@"run task 2");
        sleep(1);
        NSLog(@"complete task 2");
        dispatch_semaphore_signal(semaphore);       
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    //任务3
    dispatch_async(quene, ^{
        NSLog(@"run task 3");
        sleep(1);
        NSLog(@"complete task 3");   
        dispatch_semaphore_signal(semaphore);   
    });  
    NSLog(@"-------->end");
}
复制代码

执行结果

run task 1

complete task 1

run task 2

complete task 2

run task 3

complete task 3

——–>end

② 通过dispatch_semaphore_create的值控制同一时间执行的线程数
/*
  dispatch_semaphore_create的value表示,最多几个资源可访问
  如果信号值设为2,先执行两个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2。
  如果设定的信号值为3,在这个方法里就是不限制线程执行了,因为一共才只有3个线程。
*/
-(void)dispatchSignal{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);   
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     
    //任务1
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 1");
        sleep(2);
        NSLog(@"complete task 1");
        dispatch_semaphore_signal(semaphore);       
    });
    //任务2
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 2");
        sleep(1);
        NSLog(@"complete task 2");
        dispatch_semaphore_signal(semaphore);       
    });
    //任务3
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 3");
        sleep(1);
        NSLog(@"complete task 3");
        dispatch_semaphore_signal(semaphore);       
    });   
}
复制代码

执行结果

run task 1

run task 2

complete task 2

complete task 1

run task 3

complete task 3

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享