GCD

120 阅读7分钟

进程

系统中正在运行的应用程序,如微信,支付宝都是一个进程,进程之间是相互独立的

线程

线程是进程执行的基本单元,一个进程上面的所有任务都是在线程上面执行的,程序启动默认开发一条主线程

队列

Dispatch Queue:用来存放任务的队列,队列是一种特殊的线性表,采用FIFO(先进先出)的原则

GCD队列类型

主队列

dispatch_get_main_queue

全局并发队列

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)

自定义队列

串行队列

dispatch_queue_create("com.mr",DISPATCH_QUEUE_SERIAL);

并发队列

dispatch_queue_create("com.mr",DISPATCH_QUEUE_CONCURRENT);

队列和线程的关系

  • 队列负责任务调度
  • 线程负责任务执行

任务

任务就是执行操作的意思,也就是在线程中执行的那段代码,在GCD中是放在block中的,执行任务有两种方式:

  • 同步执行(sync)无开子线程能力:在添加的任务执行结束之前,会一直等待
  • 异步执行(async)有开子线程能力:线程会立即返回,无需等待就会继续执行下面的任务,不会阻塞当前线程 总结:dispatch_async(异步)和dispatch_sync(同步):决定有没有开子线程的能力,而不是指会不会开子线程。queue 决定有没有并发的能力。开子线程就并发,不开就串行。

同步串行

dispatch_queue_t serial_queue = dispatch_queue_create("com.mr",DISPATCH_QUEUE_SERIAL);
dispatch_sync(serial_queue,^{//任务});

异步串行

dispatch_queue_t serial_queue = dispatch_queue_create("com.mr",DISPATCH_QUEUE_SERIAL);
dispatch_async(serial_queue,^{//任务});

同步并发

dispatch_queue_t queue = dispatch_queue_create("com.mr",DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue,^{//任务});

异步并发

dispatch_queue_t queue = dispatch_queue_create("com.mr",DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue,^{//任务});

GCD面试题

同步遇到了串行

-(void)viewDidLoad {
    dispatch_queue_t queue = dispatch_get_main_queue();
    NSLog(@"1");// 任务1
    dispatch_sync(queue, ^{
        NSLog(@"2");// 任务2
    });
    NSLog(@"3");// 任务3
}

对于这块产生死锁的原因,网上教程很多,最多的一个是如下: 首先执行任务1,这是肯定没有问题的,接下来,程序遇到了同步线程,那么它会进入等待,等待任务2执行完,在执行任务3,但是这是主队列,是一个特殊的串行队列,有任务来,就会将任务添加到队尾,遵循先进先出的原则,那么,现在任务2就会被加到最后,任务3排在任务2的前面,导致死锁。

个人感觉上面的分析是错误的,我们可以用代码直接证明,去掉任务3,如下:我们发现还是产生了死锁

-(void)viewDidLoad {
    dispatch_queue_t queue = dispatch_get_main_queue();
    NSLog(@"1");// 任务1
    dispatch_sync(queue, ^{
        NSLog(@"2");// 任务2
    });
}

我这边的理解是:代码在viewDidLoad中执行,NSLog(@"1")正常输出,之后遇到dispatch_sync,这个代码在 主线程执行,同时block任务也是主线程中执行,主线程是串行队列,先进先出的原则,dispatch_sync先加入 block任务后加入,所以先执行dispatch_sync,后执行block任务,但是dispatch_sync必须等待block执行 完成,才会执行,导致了相互等待,最后死锁,这只是个人理解,还望对这块理解深刻的大佬指点~

同步遇到了并行

-(void)viewDidLoad {
    NSLog(@"1"); // 任务1
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"2"); // 任务2
    });
    NSLog(@"3"); // 任务3
}

输出结果

1
2
3

这个很好理解,首先执行任务1,接下来一个并行队列同步执行,输出2,最后输出3

同步异步都有

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_queue_create("com.mr", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1"); // 任务1
    dispatch_async(queue, ^{
        NSLog(@"2"); // 任务2
        dispatch_sync(queue, ^{
            NSLog(@"3"); // 任务3
        });
        NSLog(@"4"); // 任务4
    });
    NSLog(@"5"); // 任务5
    //1 5 2
}

任务2 任务3 任务4这里产生死锁,和第一个例子同理,第一个例子是在主线程中,这里是在一个子线程中

异步遇到同步回主线程

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1"); // 任务1
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2"); // 任务2
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"3"); // 任务3
        });
        NSLog(@"4"); // 任务4
    });
    NSLog(@"5"); // 任务5
    //1 5 2 3 4
}

GCD的线程同步方案

栅栏函数

我们在并发异步函数的时候,我们想要先执行完某一个或者几个任务之后在执行下面的任务,我们就可以使用栅栏函数,栅栏函数传入的队列必须是dispatch_queue_create创建的使用dispatch_get_global_queue会有问题 截屏2022-02-15 18.36.14.png 上图箭头就是栅栏函数,我们想要执行完任务一和任务二之后在执行任务三

#import "ViewController.h"
@interface ViewController ()
@property(nonatomic,strong)NSMutableArray *arr;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.arr = [[NSMutableArray alloc]initWithObjects:@"1",@"2",@"3", nil];
    dispatch_queue_t queue = dispatch_queue_create("com.mr", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        //任务1
        NSLog(@"%@",self.arr);
    });
    dispatch_async(queue, ^{
        //任务2
        NSLog(@"%@",self.arr);
    });
    dispatch_barrier_async(queue, ^{
        [self.arr addObject:@"4"];
        [self.arr addObject:@"5"];
    });
    dispatch_async(queue, ^{
        //任务3
        NSLog(@"%@",self.arr);
    });
}
@end

输出如下:

2022-02-15 18:55:48.759533+0800 OCProject[24114:375362] (
    1
)
2022-02-15 18:55:48.759538+0800 OCProject[24114:375357] (
    1
)
2022-02-15 18:55:48.759741+0800 OCProject[24114:375357] (
    1,
    2,
    3
)

调度组

主要用与线程同步,比如多个异步网络请求,全部请求完成进行汇总处理

#import "ViewController.h"
@interface ViewController ()
@property(nonatomic,strong)NSMutableArray *arr;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    __block NSInteger num = 0;
    dispatch_group_t group = dispatch_group_create();
    //耗时操作1
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(5);
        NSLog(@"耗时操作完成");
        num = num + 10;
        [self.arr addObject:@"2"];
    });
    //网络请求1
    dispatch_group_enter(group);
    [self sendRequestWithComplention:^(id response) {        NSLog(@"网络请求1完成");        num = num + 5;        dispatch_group_leave(group);    }];
    //网络请求2
    dispatch_group_enter(group);
    [self sendRequestWithComplention:^(id response) {        NSLog(@"网络请求2完成");        num = num + 3;        dispatch_group_leave(group);    }];
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"%ld",(long)num);
    });
    
}
-(void)sendRequestWithComplention:(void (^)(id response))completion{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        sleep(1);
        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                completion(@"完成");
            }
        });
    });
}
@end

输出如下,达到了异步线程执行网络请求,最后汇总执行结果的目的

OCProject[24664:403262] 网络请求2完成
OCProject[24664:403262] 网络请求1完成
OCProject[24664:403405] 耗时操作完成
OCProject[24664:403262] 18

信号量

作用:控制GCD最大并发数,以及控制GCD执行顺序

  • 控制最大并发数
    dispatch_semaphore_t sem = dispatch_semaphore_create(2);
    dispatch_queue_t queue = dispatch_queue_create("com.mr", DISPATCH_QUEUE_CONCURRENT);
    //任务1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务1");
        sleep(1);
        NSLog(@"任务1执行完成");
        dispatch_semaphore_signal(sem);
    });
    //任务2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务2");
        sleep(1);
        NSLog(@"任务2执行完成");
        dispatch_semaphore_signal(sem);
    });
    //任务3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务3");
        sleep(1);
        NSLog(@"任务3执行完成");
        dispatch_semaphore_signal(sem);
    });
    //任务4
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务4");
        sleep(1);
        NSLog(@"任务4执行完成");
        dispatch_semaphore_signal(sem);
    });

控制最大并发数为2,每次最多执行2个任务

2022-02-15 22:51:35.298210+0800 GCD[5128:74679] 执行任务1
2022-02-15 22:51:35.298210+0800 GCD[5128:74680] 执行任务2
2022-02-15 22:51:36.302372+0800 GCD[5128:74680] 任务2执行完成
2022-02-15 22:51:36.302372+0800 GCD[5128:74679] 任务1执行完成
2022-02-15 22:51:36.302743+0800 GCD[5128:74682] 执行任务3
2022-02-15 22:51:36.302743+0800 GCD[5128:74681] 执行任务4
2022-02-15 22:51:37.306466+0800 GCD[5128:74682] 任务3执行完成
2022-02-15 22:51:37.306466+0800 GCD[5128:74681] 任务4执行完成
  • 控制执行顺序
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    dispatch_queue_t queue = dispatch_queue_create("com.mr", DISPATCH_QUEUE_CONCURRENT);
    //任务1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务1");
        NSLog(@"任务1执行完成");
    });
    //任务2
    dispatch_async(queue, ^{
        NSLog(@"执行任务2");
        sleep(1);
        NSLog(@"任务2执行完成");
        dispatch_semaphore_signal(sem);
    });

控制先执行任务2,再执行任务1

2022-02-15 22:54:59.704199+0800 GCD[5303:77593] 执行任务2
2022-02-15 22:55:00.709321+0800 GCD[5303:77593] 任务2执行完成
2022-02-15 22:55:00.709667+0800 GCD[5303:77596] 执行任务1
2022-02-15 22:55:00.709875+0800 GCD[5303:77596] 任务1执行完成

总结:

  • dispatch_semaphore_create 创建一个semaphore
  • dispatch_semaphore_signal   发送一个信号
  • dispatch_semaphore_wait    等待信号 dispatch_semaphore_signal是发送一个信号,会让信号总量加1,dispatch_semaphore_wait等待信号,会让信号量减1,当信号总量少于0的时候就会一直等待,否则就可以正常的执行