iOS 底层原理 iOS-多线程iOS 进阶之路

OC底层原理二十六:GCD详解(上)

2020-11-04  本文已影响0人  markhetao

OC底层原理 学习大纲

上一节我们分析了多线程的知识,本节,我们着重分析多线程中使用最频繁GCD

  1. GCD简介
  2. 函数与队列四大组合(同步、异步、串行、并行)
  3. 性能调度耗能
  4. 面试题
  5. 线程资源共享
  6. 栅栏函数barrier
  7. 调度组 Group
  8. GCD单例
  9. 信号量 semaphore

1. GCD简介

GCD,全称Grand Central Dispatch(中央调度中心),纯C语言开发,提供了很多强大的函数

  1. GCD是苹果公司为多核并行运算提出的解决方案
  2. GCD会自动利用更多的CPU内核(比如双核、四核等);
  3. GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)。

程序员只需要告诉GCD想要执行的任务不需要编写任何线程管理相关代码(调度销毁不用管)

这里引申出任务队列执行任务的函数三个内容。我们一一进行分析

首先,我们展示一个简单示例

- (void)syncTest {

    // 任务(block)
    dispatch_block_t block = ^{
        NSLog(@"hello GCD");
    };

    // 队列(此处串行队列)
    dispatch_queue_t queue = dispatch_queue_create("ht-syncTest", DISPATCH_QUEUE_SERIAL);

    // 执行任务的函数(此处异步函数)
    dispatch_async(queue, block);

}

1.1 任务

GCD的任务是使用block封装的函数没有入参返参

拓展:

  • 执行block,需要调用block(),这步调用,是执行任务的函数内部自动管理
    后面解析dispatch源码时,可以清楚知道调用时机

1.2 队列

GCD的队列包含串行队列并行队列两种。

image.png

1.3 执行任务的函数

执行任务的函数包括同步函数异步函数两种:

1.3.1 dispatch_sync同步函数:
1.3.2 dispatch_async异步函数:

异步多线程代名词

  • 多线程意义,就是为了适当提高执行效率,开启多个线程"同时"执行多个任务
  • 严格来说,应该是并行异步多线程的代名词。因为只有并行,才支持多通道(车道),才能同时执行多个任务

ps: (下文中提到的函数,都指代执行任务的函数

为了更好的理解这些概念,下面对函数与队列四大组合一一进行案例分析

2. 函数与队列四大组合(同步、异步、串行、并行)

未命名.png
  1. 专门用来在主线程调度任务串行队列
  2. 开启线程
  3. 如果当前主线程正在执行任务,需要当前任务执行完,才会继续调度其他任务。
  1. 为了方便程序员的使用,苹果提供了全局队列 (并发队列,实现多线程需求的快捷方式)。
  2. 使用多线程开发时,如果对队列没有特殊要求,可直接使用全局队列来执行异步任务。)

拓展:
Q:队列有几种?

- (void)demo {
    // 串行队列
   dispatch_queue_t serial = dispatch_queue_create("ht", DISPATCH_QUEUE_SERIAL);
   // 并行队列
   dispatch_queue_t concurrent = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
   // 主队列(串行队列)
   dispatch_queue_t mainQueue = dispatch_get_main_queue();
   // 全局队列 (并行队列)
   dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

   NSLog(@"\n%@ \n%@ \n%@ \n%@", serial, concurrent, mainQueue, globalQueue);
}
image.png
  • A:只有串行队列并行队列两种。
    (底层: DQF_WIDTH为1:表示串行队列DQF_WIDTH大于1: 表示并行队列 。详细底层分析,下一节会讲)

2.1 同步 + 串行 死锁

- (void)mainSyncTest{
    
    NSLog(@"0 %@", [NSThread currentThread]);
    // 等
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"1 %@", [NSThread currentThread]);
    });
    NSLog(@"2 %@", [NSThread currentThread]);
}
image.png
  1. 主队列(main)是串行队列,函数是sync同步函数。属于同步函数&串行队列的情况
  2. 在打印0之后,dispatch_sync同步函数将block排队插入mainSyncTest函数最后,等待mainSyncTest函数执行完后再执行。
  3. 但是block没有执行,dispatch_sync函数就等于没有完成。程序无法往下执行。
  4. 所以造成了dispatch_syncmainSyncTest执行完后执行block,而mainSyncTest却说dispatch_sync没有执行完,我无法结束。 😂
image.png

这里有一个误区,堵塞打印2无关。

image.png

真正的堵塞,是由于dispatch_sync内部的block需要等mainSyncTest全部执行完再执行,而mainSyncTest函数需要等dispatch_sync执行完。

  • 借用一个笑话描述:
    面试官:你讲清楚了GCD的底层原理,我就录用你
    大牛:你录用我,我就给你讲GCD的底层原理

2.2 同步 + 并行

- (void)globalSyncTest{
    
    for (int i = 0; i<20; i++) {
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"hello queue");
}
image.png

2.3 异步 + 串行

- (void)mainAsyncTest{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"1 %@", [NSThread currentThread]);
    });
    NSLog(@"2 %@", [NSThread currentThread]);
}
image.png

可以对比上面2.1 同步 + 串行 阻塞死锁的现象,两者的区别是:

同步 + 串行:

  1. dispatch_sync必须 mainSyncTest执行完,才将block任务插入尾部。

  2. dispatch_sync必须 block执行完,才算完成。

异步 + 串行:

1.dispatch_async 不用等 mainAsyncTest执行完,直接将block任务插入尾部。

  1. dispatch_async 不用等 block执行完,只要将block插入尾部,就算完成了。

2.3 异步 + 并行

- (void)globalSyncTest{

    for (int i = 0; i<20; i++) {
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"hello queue");
}
image.png

3. 性能调度耗能

测试代码:

- (void)dissipation {
    
    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    
    dispatch_queue_t queue = dispatch_queue_create("ht-thread", DISPATCH_QUEUE_SERIAL);
    
//    dispatch_async(queue, ^{
//        NSLog(@"异步执行");
//    });
    
    dispatch_sync(queue, ^{
        NSLog(@"同步执行");
    });
    
    NSLog(@"%f", CFAbsoluteTimeGetCurrent() - time);
    
}
  1. 无任何操作时,基本无耗时

    image.png
  2. 创建线程: 耗时0.00009秒

    image.png
  3. 创建线程且调用异步函数: 耗时0.00040秒

    image.png
  4. 创建线程且调用同步函数: 耗时0.000232秒

    image.png

结论

  1. 每次创建线程,都会有时间上的损耗
  2. 线程创建后,同步执行异步执行耗时

4. 面试题

4.1 面试题一

- (void)demo{
    // 串行队列
    dispatch_queue_t queue = dispatch_queue_create("ht", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    // 异步函数
    dispatch_async(queue, ^{
        NSLog(@"2");
        // 同步
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
    });
    NSLog(@"5");
}
image.png

如果你掌握上面内容,特别[图片上传中...(未命名.png-beed7-1604486756600-0)]
是我总结的同步 + 串行异步 + 串行的区别熟悉了。这题就难不住你了。

分析:


image.png

4.2 面试题二

- (void)textDemo{
    // 并行队列
    dispatch_queue_t queue = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1 %@",[NSThread currentThread]);
    // 异步
    dispatch_async(queue, ^{
        NSLog(@"2 %@",[NSThread currentThread]);
        // 同步
        dispatch_sync(queue, ^{
            NSLog(@"3 %@",[NSThread currentThread]);
        });
        NSLog(@"4 %@",[NSThread currentThread]);
    });
    NSLog(@"5 %@",[NSThread currentThread]);
}
image.png

4.3 面试题三

- (void)textDemo{
    // 并行队列
    dispatch_queue_t queue = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    // 耗时
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}
image.png

4.4 面试题四

- (void)demo{
    // 并行队列
    dispatch_queue_t queue = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"1");
    });
    dispatch_async(queue, ^{
        NSLog(@"2");
    });
    
    // 同步
    dispatch_sync(queue, ^{
        NSLog(@"3");
    });
    
    NSLog(@"0");

    dispatch_async(queue, ^{
        NSLog(@"7");
    });
    dispatch_async(queue, ^{
        NSLog(@"8");
    });
    dispatch_async(queue, ^{
        NSLog(@"9");
    });
}
image.png

分析:

  1. 并发队列
  2. 异步 & 并发无序的,所以12的打印是无序的, 789的打印是无序的;
  3. 同步 & 并发排队一个个任务执行,所以0一定在3后面打印,7、8、9一定在0后面打印。

满足03后打印,7、8、90后打印。只有选项 AC


5. 线程资源共享

@interface ViewController ()
@property (nonatomic, assign) NSInteger tickets;      // 票数
@property (nonatomic, strong) dispatch_queue_t queue; // 队列
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 准备票数
    _tickets = 20;
    // 创建串行队列
    _queue = dispatch_queue_create("ht", DISPATCH_QUEUE_SERIAL);
    
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    // 第一个线程卖票
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self saleTickes];
    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 第二个线程卖票
        [self saleTickes];
    });

}

- (void)saleTickes {
    
    while (self.tickets > 0) {
        // 模拟延时
        [NSThread sleepForTimeInterval:1.0];
        // 苹果不推荐程序员使用互斥锁,串行队列同步任务可以达到同样的效果!
        // @synchronized
        // 使用串行队列,同步任务卖票
        dispatch_sync(_queue, ^{
            // 检查票数
            if (self.tickets > 0) {
                self.tickets--;
                NSLog(@"还剩 %zd %@", self.tickets, [NSThread currentThread]);
            } else {
                NSLog(@"没有票了");
            }
        });
    }
}
@end

6. 栅栏函数barrier

控制任务执行顺序同步

重点:栅栏函数只能控制同一并发队列

坑点:栅栏函数为何不能使用dispatch_get_global_queue队列?

因为global队列中有很多系统任务也在执行。 我们需要dispatch_queue_create手动创建一个纯净队列,放置自己需要执行的任务,再使用栅栏函数监听任务的执行结果

//MARK: -ViewController
@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    __block CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    
    // 请求token
    [self requestToken:^(id value) {
        // 带token
        [weakSelf requestDataWithToken:value handle:^(BOOL success) {
            success ? NSLog(@"成功") : NSLog(@"失败");
            NSLog(@"%f", CFAbsoluteTimeGetCurrent() - time);
        }];
    }];
}

/** 获取token请求 */
- (void)requestToken:(void(^)(id value))successBlock{
    NSLog(@"开始请求token");
    [NSThread sleepForTimeInterval:1];
    if (successBlock) {
        successBlock(@"b2a8f8523ab41f8b4b9b2a79ff47c3f1");
    }
}

/** 请求所有数据 */
- (void)requestDataWithToken: (NSString *)token handle: (void(^)(BOOL success))successBlock {
    dispatch_queue_t queue = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        [self requestHeadDataWithToken: token handle:^(id value) { NSLog(@"%@", value); }];
    });
    
    dispatch_async(queue, ^{
        [self requestListDataWithToken:token handle:^(id value) { NSLog(@"%@", value); }];
    });
    
    dispatch_barrier_async(queue, ^{  successBlock(true); });
    
}

/** 头部数据的请求 */
- (void)requestHeadDataWithToken:(NSString *)token handle:(void(^)(id value))successBlock{
    if (token.length == 0) {
        NSLog(@"没有token,因为安全性无法请求数据");
        return;
    }
    [NSThread sleepForTimeInterval:2];
    if (successBlock) {
        successBlock(@"我是头,都听我的");
    }
}
/** 列表数据的请求 */
- (void)requestListDataWithToken:(NSString *)token handle:(void(^)(id value))successBlock{
    if (token.length == 0) {
        NSLog(@"没有token,因为安全性无法请求数据");
        return;
    }
    [NSThread sleepForTimeInterval:1];
    if (successBlock) {
        successBlock(@"我是列表数据");
    }
}
@end

7. 调度组 Group

栅栏函数类似,也是控制任务的执行顺序

//MARK: -ViewController
@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    __block CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    
    // 1. 【手动入组和出组】
    [self requestToken:^(id value) {
        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t concurrent = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);

        dispatch_group_enter(group);
        dispatch_async(concurrent, ^{
            [weakSelf requestHeadDataWithToken:value handle:^(id value) {
                NSLog(@"%@",value);
                dispatch_group_leave(group);
            }];
        });

        dispatch_group_enter(group);
        dispatch_async(concurrent, ^{
            [weakSelf requestListDataWithToken:value handle:^(id value) {
                NSLog(@"%@",value);
                dispatch_group_leave(group);
            }];
        });

        dispatch_group_notify(group, concurrent, ^{
            NSLog(@"成功了");
            NSLog(@"%f", CFAbsoluteTimeGetCurrent() - time);
        });
    }];
    
//    // 2. 【自动入组和出组】
//    [self requestToken:^(id value) {
//        dispatch_group_t group = dispatch_group_create();
//        dispatch_queue_t concurrent = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
//
//        dispatch_group_async(group, concurrent, ^{
//            [weakSelf requestHeadDataWithToken:value handle:^(id value) {
//                NSLog(@"%@",value);
//            }];
//        });
//
//        dispatch_group_async(group, concurrent, ^{
//            [weakSelf requestListDataWithToken:value handle:^(id value) {
//                NSLog(@"%@",value);
//            }];
//        });
//
//        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//            NSLog(@"成功了");
//            NSLog(@"%f", CFAbsoluteTimeGetCurrent() - time);
//        });
//
//    }];
    
//    // 3. 【同步函数 + 自动入组出组】
//    __block NSString * token;
//    dispatch_sync(dispatch_queue_create("ht", DISPATCH_QUEUE_SERIAL), ^{
//        [self requestToken:^(id value) {
//            token = value;
//        }];
//    });
//
//    dispatch_group_t group = dispatch_group_create();
//    dispatch_queue_t concurrent = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
//
//    dispatch_group_async(group, concurrent, ^{
//        [weakSelf requestHeadDataWithToken: token handle:^(id value) {
//            NSLog(@"%@",value);
//        }];
//    });
//
//    dispatch_group_async(group, concurrent, ^{
//        [weakSelf requestListDataWithToken: token handle:^(id value) {
//            NSLog(@"%@",value);
//        }];
//    });
//
//    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//        NSLog(@"成功了");
//        NSLog(@"%f", CFAbsoluteTimeGetCurrent() - time);
//    });
    
}

/** 获取token请求 */
- (void)requestToken:(void(^)(id value))successBlock{
    NSLog(@"开始请求token");
    [NSThread sleepForTimeInterval:1];
    if (successBlock) {
        successBlock(@"b2a8f8523ab41f8b4b9b2a79ff47c3f1");
    }
}

/** 头部数据的请求 */
- (void)requestHeadDataWithToken:(NSString *)token handle:(void(^)(id value))successBlock{
    if (token.length == 0) {
        NSLog(@"没有token,因为安全性无法请求数据");
        return;
    }
    [NSThread sleepForTimeInterval:2];
    if (successBlock) {
        successBlock(@"我是头,都听我的");
    }
}
/** 列表数据的请求 */
- (void)requestListDataWithToken:(NSString *)token handle:(void(^)(id value))successBlock{
    if (token.length == 0) {
        NSLog(@"没有token,因为安全性无法请求数据");
        return;
    }
    [NSThread sleepForTimeInterval:1];
    if (successBlock) {
        successBlock(@"我是列表数据");
    }
}
@end

8. GCD单例

  1. 利用static内存中仅一份的特性,保证了对象的唯一性
  2. 重写allocWithZone的实现,让外界使用alloc创建时,永远返回的是static声明的对象
#import "KCImageManger.h"
// 保存在常量区
static id instance;

@implementation KCImageManger


/**
 每次类初始化的时候进行调用

 1、+load它不遵循那套继承规则。如果某个类本身没有实现+load方法,那么不管其它各级超类是否实现此方法,系统都不会调用。+load方法调用顺序是:SuperClass -->SubClass --> CategaryClass。
 
 3、+initialize是在类或者它的子类接受第一条消息前被调用,但是在它的超类接收到initialize之后。也就是说+initialize是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的+initialize方法是不会被调用的。
 
 4、只有执行+initialize的那个线程可以操作类或类实例,其他线程都要阻塞等着+initialize执行完。
 5、+initialize  本身类的调用都会执行父类和分类实现 initialize方法都会被调多次
 
 */
+ (void)initialize{
    NSLog(@"父类");
    if (instance == nil) {
        instance = [[self alloc] init];
    }
}
/**
 配合上面 也能进行单利
 */
+ (instancetype)manager{
    return instance;
}

/**
 * 所有为类的对象分配空间的方法,最终都会调用到 allovWithZone 方法
 * 下面这样的操作相当于锁死 该类的所有初始化方法
 */
+(instancetype)allocWithZone:(struct _NSZone *)zone{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [super allocWithZone:zone];
    });
    return instance;
}

/**
 单利
 */
+(instancetype)shareManager{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

@end
- (void)onceDemo{
//    KCImageManger *manger1 = [KCImageManger shareManager];
//    KCImageManger *manger2 = [KCImageManger shareManager];
//    KCImageManger *manger3 = [KCImageManger shareManager];
    
//    KCImageManger *manger1 = [[KCImageManger alloc] init];
//    KCImageManger *manger2 = [[KCImageManger alloc] init];
//    KCImageManger *manger3 = [KCImageManger new];
    
    KCImageManger *manger1 = [[KCImageManger alloc] init];
    KCImageManger *manger2 = [KCImageManger manager];
    KCImageManger *manger3 = [KCImageManger manager];
    
    NSLog(@"%@---%@---%@",manger1,manger2,manger3);
}
image.png

9. 信号量 semaphore

控制GCD最大并发数。(同一时刻可进行的信号(任务)最大个数。)

加入了信号量的等待dispatch_semaphore_wait后,一定需要配对加入信号量释放dispatch_semaphore_signal,不然会crash

- (void)viewDidLoad {
    [super viewDidLoad];
    // 创建全局队列(并行)
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
   // 设置信号量
    dispatch_semaphore_t sem = dispatch_semaphore_create(2); // 最多同时执行2个任务
    
    //任务1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        sleep(1);
        NSLog(@"执行任务1");
        sleep(1);
        NSLog(@"任务1完成");
        dispatch_semaphore_signal(sem);
    });
    
    //任务2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        sleep(1);
        NSLog(@"执行任务2");
        sleep(1);
        NSLog(@"任务2完成");
        dispatch_semaphore_signal(sem);
    });
    
    //任务3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        sleep(1);
        NSLog(@"执行任务3");
        sleep(1);
        NSLog(@"任务3完成");
        dispatch_semaphore_signal(sem);
    });
}

下一节,分析 dispatch源码

上一篇 下一篇

猜你喜欢

热点阅读