likes
comments
collection
share

iOS 多线程之 GCD

作者站长头像
站长
· 阅读数 32

前序:按顺序阅读更好

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_PRIORITY_DEFAULT=0
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

1.2 任务的创建方法

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

2. 死锁的情况

2.1 第一种

主队列+同步操作 主队列造成死锁的原因同第二种. 因为主队列其实是特殊的串行队列

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

2.2 第二种

异步操作+串行队列的任务中,又嵌套了同步操作+当前的串行队列

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.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_time_t管理时间,而不是NSTimer。所以如果在子线程中调用,相比performSelector:afterDelay,不用关心runloop是否开启。

注意: performSelector:afterDelay这个延迟函数,会在内部创建一个NSTimer,然后添加到当前线程的runloop中,也就是如果当前线程没有开启runloop这个方法会失效(子线程中需要手动启用runloop)。而performSelector只是一个单纯的消息发送,和时间没有一点关系,所以不需要添加到子线程的runloop也能执行。

/**
 * 延时执行方法 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_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执行结果如下==================//
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_sync执行结果如上==================//

//==================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
//==================dispatch_barrier_async执行结果如上==================//
  • 共同点
    • 先等任务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 次的代码(这里面默认是线程安全的)
    });
}
//dispatch_once在单例中的应用
+ (TMProgressHUD *)sharedInstance {
    static TMProgressHUD *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [self new];
    });
    return sharedInstance;
}

4.3 GCD 快速迭代方法:dispatch_apply

dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API.该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。

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

  • 如果是在并发队列进行异步执行,for 循环的做法是按顺序逐个遍历。dispatch_apply 可以 在多个线程中同时(异步)遍历(乱序)。

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

  • dispatch_apply加到主队列中会死锁(因为dispatch_apply实质是dispatch_sync函数和Dispatch Group的关联API)

    dispatch_apply(6, dispatch_get_main_queue(), ^(size_t index){});

/**
 * 快速迭代方法 dispatch_apply
 * 遍历 0~5 这 6 个数字
 * dispatch_get_global_queue 是全局并发队列
 * 乱序遍历 ,但是apply---end最后执行
 */
- (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_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");
}

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");
    });
}

4.5 GCD 信号量:dispatch_semaphore

  • dispatch_semaphore相关的3个函数
    • dispatch_semaphore_create: 创建一个Semaphore并初始化信号的总量(不能小于 0);
    • dispatch_semaphore_wait: 使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行;
    • dispatch_semaphore_signal: 发送一个信号,让信号总量加1
  • 发送信号(signal)与等待信号(wait)往往是成对出现的。
  • dispatch_semaphore的实际应用
    • 保持线程同步,将异步执行任务转换为同步执行任务
    • 保证线程安全,为线程加锁
    • 控制线程并发数
① 保持线程同步,将异步执行任务转换为同步执行任务

保持线程同步,也就是通过dispatch_semaphore控制异步任务一个执行完再执行另一个

-(void)dispatchSignal{

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //任务1
    dispatch_async(queue, ^{
        printf("run task 1 \n");
        sleep(2);
        printf("complete task 1 \n");
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    printf("分割线1 \n");

    //任务2
    dispatch_async(queue, ^{
        printf("run task 2 \n");
        sleep(1);
        printf("complete task 2 \n");
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    printf("分割线2 \n");

    //任务3
    dispatch_async(queue, ^{
        printf("run task 3 \n");
        sleep(1);
        printf("complete task 3 \n");
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
   
    printf("分割线3 \n");   

    //任务4
    dispatch_async(queue, ^{
        printf("run task 4 \n");
        sleep(3);
        printf("complete task 4 \n");
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    printf("分割线4 \n");
    printf("=====end======\n");
}
执行结果
run task 1
complete task 1
分割线1
run task 2
complete task 2
分割线2
run task 3
complete task 3
分割线3
run task 4
complete task 4
分割线4
=====end======

再举一个保持线程同步的例子: dispatch_semaphore_wait加锁阻塞了当前线程,dispatch_semaphore_signal解锁后当前线程继续执行。

//
-(void)dispatchSignal3{

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    __block int number = 1;
    dispatch_async(queue, ^{
        number = 6;
        dispatch_semaphore_signal(semaphore);
    });  

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"number--->%d",number);
}

② 保证线程安全,为线程加锁

首先创建全局变量,初始化信号量为1。在线程安全中可以将dispatch_semaphore_wait看作加锁,而dispatch_semaphore_signal看作解锁。

//1. 初始化全局变量
_semaphore = dispatch_semaphore_create(1);

-(void)asyncTask{
    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);//当前信号量-1

    printf("执行任务,这个任务需要加锁");
    //线程休眠  C语言:sleep(1); OC:[NSThread sleepForTimeInterval:1];
    [NSThread sleepForTimeInterval:1];
   
    dispatch_semaphore_signal(_semaphore);//当前信号量+1
}

-(void)dispatchSignal4{
    for (int i = 0; i < 100; i++){
         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
              [self asyncTask];
           });
      }
}
③ 通过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

转载自:https://juejin.cn/post/6978124101386944542
评论
请登录