iOS底层-25:GCD及使用

简介

GCD全称Drand Central Dispatch

  • c语言,提供了非常多强大的函数

什么是GCD

将任务添加到队列,并且指定执行任务的函数

GCD的优势:

  1. GCD是苹果公司为多核的并行运算提供的解决方案。
  2. GCD会自动利用更多的CPU内核(比如双核、四核)
  3. GCD自动管理线程周期(创建线程、调度任务、销毁线程)
    程序员只需要告诉GCD想执行什么任务,并不需要编写任何线程管理代码

基础写法

    //任务
    dispatch_block_t block = ^{
        NSLog(@"hello world");
    };
    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);
    //异步函数
    dispatch_async(queue, block);

这就是将任务添加到队列,并通过异步函数执行。

  • 任务使用block封装,任务的block没有参数也没有返回值。

  • 异步函数 dispatch_async
    不用等待当前语句执行完毕,就可以执行下一条语句

    • 会开启线程执行block任务,异步是多线程的代名词
  • 同步函数 dispatch_sync
    必须等到当前语句执行完毕,才可以执行下一条语句

    • 不会开启线程,在当前线程中执行block任务

同步和异步耗能问题

同步函数

    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);

    //同步函数
    dispatch_sync(queue, ^{
   
    });
    
    NSLog(@"%f",CFAbsoluteTimeGetCurrent() - time);

打印结果:

 0.000008

异步函数

    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);

    //异步函数
    dispatch_async(queue, ^{
//        NSLog(@"任务1");
    });
    
    NSLog(@"%f",CFAbsoluteTimeGetCurrent() - time);
 0.000027

空任务时,同步函数耗时更少。

添加一个NSLog任务

  • 同步函数
    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);

    //同步函数
    dispatch_sync(queue, ^{
        NSLog(@"任务1");
    });
    
    NSLog(@"%f",CFAbsoluteTimeGetCurrent() - time);

打印结果

 任务1
 0.000099
  • 异步函数
    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);

    //异步步函数
    dispatch_async(queue, ^{
        NSLog(@"任务1");
    });
    
    NSLog(@"%f",CFAbsoluteTimeGetCurrent() - time);

打印结果:

 0.000025
  任务1

一个简简单单的打印语句,同步函数耗费时间远大于异步函数。

同步和异步函数都会耗费时间,异步函数多用来解决并发和多线程问题。

队列

串行队列:按照任务指派的顺序来执行任务,前一个执行完,下一个才能执行
并行队列:能够同时执行一个或多个任务执行任务的顺序不一定

切勿将串行队列、并行对队列和同步、异步混淆

函数与队列搭配

函数与队列搭配使用
同步函数与串行队列
    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);
    //同步函数
    
    dispatch_sync(queue, ^{
        NSLog(@"任务1");
        sleep(2);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"任务2");
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"任务3");
    });

打印结果:

 任务1
 任务2
 任务3
同步函数与并行队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //同步函数
    
    dispatch_sync(queue, ^{
        NSLog(@"任务1");
        sleep(2);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"任务2");
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"任务3");
    });

打印结果

 任务1
 任务2
 任务3
异步函数与串行队列
    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);
    //同步函数
    
    dispatch_async(queue, ^{
        NSLog(@"任务1");
        sleep(2);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"任务2");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"任务3");
      
    });
    dispatch_async(queue, ^{
        NSLog(@"任务4");
      
    });
    dispatch_async(queue, ^{
        NSLog(@"任务5");
       
    });
异步函数与并行队列
    //并行队列 
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //同步函数
    
    dispatch_async(queue, ^{
        NSLog(@"任务1");
        sleep(2);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"任务2");
        sleep(2);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"任务3");
        sleep(2);
    });
    dispatch_async(queue, ^{
        NSLog(@"任务4");
        sleep(2);
    });
    dispatch_async(queue, ^{
        NSLog(@"任务5");
        sleep(2);
    });

打印结果

任务2
任务1
任务3
任务4
任务5

面试题

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", 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_queue_t queue = dispatch_queue_create("com.ly",   DISPATCH_QUEUE_CONCURRENT);

    NSLog(@"任务1");
    dispatch_async(queue, ^{
        NSLog(@"任务2");
        dispatch_sync(queue, ^{
            NSLog(@"任务3");
        });
        NSLog(@"任务4");
    });
    
    NSLog(@"任务5");

打印结果为:

任务1
任务5
任务2
任务3
任务4
  • 将队列改为串行队列
     //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);

    NSLog(@"任务1");
    dispatch_async(queue, ^{
        NSLog(@"任务2");
        dispatch_sync(queue, ^{
            NSLog(@"任务3");
        });
        NSLog(@"任务4");
    });
    
    NSLog(@"任务5");

打印结果:

任务1
任务5
任务2
崩溃

面试题2:

    dispatch_queue_t queue = dispatch_queue_create("com.ly", DISPATCH_QUEUE_CONCURRENT);

    
    dispatch_async(queue, ^{
        NSLog(@"1");
    });
    dispatch_async(queue, ^{
        NSLog(@"2");
    });
    dispatch_sync(queue, ^{
        NSLog(@"3");
    });
    NSLog(@"0");
    dispatch_sync(queue, ^{
        NSLog(@"7");
    });
    dispatch_sync(queue, ^{
        NSLog(@"8");
    });
    dispatch_sync(queue, ^{
        NSLog(@"9");
    });

答案为:AC
3必须在0之前,0在789之前,123无序 789 无序

    //A. 1230789
    //B. 1237890
    //C. 3120798
    //D. 2137890 

主队列与全局队列

主队列:

  • 专门用来在主线程上调度任务的串行队列
  • 不会开启线程
  • 如果当前主线程有任务正在执行,那么无论主队列中被添加了什么任务,都不会被调度
    -dispatch_get_main_queue()

全局队列

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

死锁现象
主线程因为你同步函数的原因先执行任务,主队列等待着主线程中的任务执行完毕,在执行自己的任务,主线程和主队列相互等待会造成死锁。

GCD用法

  1. 单例
static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"创建单例");
    });
  1. 同步/异步函数
//同步函数
 dispatch_sync(queue, ^{
        NSLog(@"3-%@",[NSThread currentThread]);
    });
//异步函数
 dispatch_async(queue, ^{
        NSLog(@"4-%@",[NSThread currentThread]);
    });
  1. dispatch_source
  • CPU负荷小,尽量不占用系统资源
  • 联结的优势
    在任一线程上调用它的一个函数dispatch_source_merge_data后,会执行dispatch_source实现定义好的句柄(可以把句柄简单理解为一个block),这个过程叫做Custom event,用户事件。是dispatch_source支持的一种事件。

句柄是一种指向指针的指针,它指向的就是一个类或者结构,它和系统有很密切的关系。HINSTANCE(实例句柄)、HBITMAP(位图句柄)、HDC(设备表述句柄)、HICON(图标句柄)等。这其中还有一个通用句柄,就是HANDLE

dispatch_source_create 创建源
dispatch_source_set_event_handler 设置源事件回调
dispatch_source_merge_data 源事件设置数据
dispatch_source_get_data 获取源事件数据
dispatch_resume 继续 
dispatch_suspend 挂起

简单例子

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.totalComplete = 0;
    
    self.queue = dispatch_queue_create("ly.com", NULL);
    // 不依赖 runloop
    // 和下层内核 kenel-workloop交互
    // DISPATCH_SOURCE_TYPE_TIMER 准确
    self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
    
    dispatch_source_set_event_handler(self.source, ^{
        
        NSLog(@"%@",[NSThread currentThread]);
        
        NSUInteger value = dispatch_source_get_data(self.source);
        self.totalComplete += value;
        NSLog(@"进度: %.2f",self.totalComplete/100.0);
        self.progressView.progress = self.totalComplete/100.0;
    });
    
    self.isRunning = YES;
    dispatch_resume(self.source);
}



- (IBAction)didClickStartOrPauseAction:(id)sender {
   
    if (self.isRunning) {
        dispatch_suspend(self.source);
        dispatch_suspend(self.queue);
        NSLog(@"已经暂停");
        self.isRunning = NO;
        [sender setTitle:@"暂停中.." forState:UIControlStateNormal];
    }else{
        dispatch_resume(self.source);
        dispatch_resume(self.queue);
        NSLog(@"已经执行了");
        self.isRunning = YES;
        [sender setTitle:@"执行中.." forState:UIControlStateNormal];
    }
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    NSLog(@"开始了");
    
    for (int i= 0; i<100; i++) {
        dispatch_async(self.queue, ^{
            if (!self.isRunning) {
                NSLog(@"已经暂停");
                return;
            }
            sleep(1);
            dispatch_source_merge_data(self.source, 1);
        });
    }
}
  1. 栅栏函数
    dispatch_async(queue, ^{
        NSLog(@"1");
        
    });
    dispatch_async(queue, ^{
        NSLog(@"2");
        NSLog(@"2-%@",[NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"3");
    });
    dispatch_async(queue, ^{
        NSLog(@"4");
    });

1、2执行完才会执行3、4
栅栏函数,堵塞的是队列

  • 保护数据写入
    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);

    for (int i = 0; i<5000; i++) {
        dispatch_async(concurrentQueue, ^{
            NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
            NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            @synchronized (self) {
                [self.mArray addObject:image];
            }
        });
    }

多线程同时向数组中写入数据时,可能会出现问题,上面使用了同步锁。也可以使用栅栏函数

    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);

    for (int i = 0; i<5000; i++) {
        dispatch_async(concurrentQueue, ^{
            NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
            NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            
            dispatch_barrier_async(concurrentQueue, ^{
                [self.mArray addObject:image];
            });
        });
    }
  • 栅栏函数不能使用全局队列
    将上面代码对列改为全局队列,运行会崩溃
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
    
    for (int i = 0; i<5000; i++) {
        dispatch_async(concurrentQueue, ^{
            NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
            NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            
            dispatch_barrier_async(concurrentQueue, ^{
                [self.mArray addObject:image];
            });
        });
    }

崩溃效果图:


原因:
栅栏函数主要是拦截队列,不让队列后面的任务执行。而系统的全局队列,不只是添加了我们写的任务,系统也会使用这个队列,添加任务被拦截才导致崩溃

  1. 调度组 dispatch_group_t
    __block int a, b;
    dispatch_queue_t queue = dispatch_queue_create("com.ly", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        a = 3;
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        b = 4;
        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"%d",a+b);
    });
  • 进组和出组必须成对存在
  • 只有当group中的任务都执行完毕才会走dispatch_group_notify里的方法
  • dispatch_group_async相当于dispatch_group_enter()dispatch_group_leave()
  1. 延迟
  2. 线程通讯
  3. 信号量/锁
    信号量的简单使用:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_semaphore_t sem = dispatch_semaphore_create(2);
    //任务1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务1");
        sleep(1);
        NSLog(@"任务1完成");
        
    });
    
    //任务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);
    });

信号量创建的大小只有2,所以1、2任务会一起执行;执行完毕之后才会执行3。

GCD不像NSOperation可以直接控制并发数,设置maxConcurrentOperationCount;它需要借助信号量来实现这一需求。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容