GCD 多线程 (一)

2018-08-18  本文已影响0人  自律改变现状

什么是 GCD ?

Grand Central Dispatch 简称 GCD,是一套底层API,提供了一种新的方法来进行并发程序编写。使用 GCD 来提升程序性能以及发挥多核系统优势。
尽管 GCD 是纯 c 语言的,但它被封装成面向对象的风格。GCD 对象被称为 dispatch object。Dispatch object 像 Cocoa 对象一样是引用计数的。

GCD 的工作原理:

让程序平行的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。

GCD 提供了很多超越传统多线程编程的优势.

GCD 中有三种队列类型:

有4个术语比较容易混淆:同步、异步、并发、串行

同步或者异步决定了要不要开启新的线程

并发和串行决定了任务的执行方式

实践

GCD 最基本且常用的函数。

// 创建一个队列
dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
// 第一个参数是一个标签,这纯是为了debug。Apple建议我们使用倒置域名来命名队列,比如“com.dreamingwish.subsystem.task”。这些名字会在崩溃日志中被显示出来,也可以被调试器调用这在调试中会很有用,所有尽量不要重名了。
// 第二个参数 设置你的队列是否串行或并行.一般我是设置NULL,它是串行。
// DISPATCH_QUEUE_SERIAL  |  DISPATCH_QUEUE_CONCURREN

dispatch_sync(dispatch_queue_create("com.buobin.task", NULL), ^{
    NSLog(@"current task %@",[NSThread currentThread]);
});

// 前面说过同步或者异步决定了要不要开启新的线程,dispatch_sync 同步添加操作。
// 那么可想而知,打印的线程应该是在当前线程。
GCD[3235:304819] current task <NSThread: 0x100604b50>{number = 1, name = main}

// 如果改为 dispatch_async 呢?
dispatch_async(dispatch_queue_create("com.buobin.task", NULL), ^{
    NSLog(@"current task %@",[NSThread currentThread]);
});

// 是的,它开辟了一个新的线程
GCD[3254:306796] current task <NSThread: 0x1006002e0>{number = 2, name = (null)}


// 创建一个全局并发队列
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"current task %@",[NSThread currentThread]);
});

再次提醒,使用 dispatch_sync 里面的 block 操作完成之后才继续执行后续代码。而 dispatch_async 无需等待 block 里面的操作是否完成, 继续执行后续代码。

一个开发场景

在一个界面中,要请求3个接口,第3个请求,需要在第1、2请求完成之后再去请求。
最简单的方法,就是一个一个的去请求。虽然这样能解决问题,但是并不能带给我们一个更好的体验。

而 GCD 可以完美的解决这个问题。

// 创建一个调度任务组 它可以将对象关联
dispatch_group_create()创建一个调度任务组 它可以将对象关联
// group 提交到的任务组,这个任务组的对象会一直持续到任务组执行完毕。
// queue 提交到的队列,任务组里不同任务的队列可以不同。
// block 提交的任务。
dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);

// group监听的任务组。
// queue 执行完毕的这个闭包所在的队列。
// block执行完毕所响应的任务。
dispatchgroup_notify( group: dispatchgroup_t!, queue: dispatch_queue_t!,_block: dispatch_block_t!)

// 示例
dispatch_group_t group = dispatch_group_create();
        
dispatch_queue_t queue1 = dispatch_queue_create("task 1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("task 2", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue3 = dispatch_queue_create("task 3", DISPATCH_QUEUE_CONCURRENT);
        
dispatch_group_async(group, queue1, ^{
    NSLog(@"请求第一个接口");
    sleep(1);
    NSLog(@"第一个接口请求成功");
});
dispatch_group_async(group, queue2, ^{
    NSLog(@"请求第二个接口");
    sleep(1);
    NSLog(@"第二个接口请求成功");
});
        
dispatch_group_notify(group, queue3, ^{
    sleep(1);
    NSLog(@"第一与第二请求完毕,请求第三个接口");
    dispatch_sync(dispatch_get_main_queue(), ^{
        sleep(1);
        NSLog(@"第三个接口请求成功,回到主线程更新界面");    
    });
});

GCD 还提供了一个简化方法叫做 dispatch_apply 这个函数可以调用单一block 多次。

模拟场景:比如我们有一个数组,里面的元素有上千个字符串,我们要判断当前字符串它是不是包含某一个字符串,如果是把它提取出来添加到一个新的数组中。直接 for 循环判断,但效率不高。

这时可以使用 dispatch_apply 解决这个问题, 充分发挥多核的优势~

// 获取一个随机整数,范围在[from,to],包括from,不包括to
int getScopeInsideRandomValue(int from, int to) {
    return (int)(from + (arc4random() % (to - from + 1)));
}

示例

        NSMutableArray *array = [NSMutableArray array];
        NSMutableArray *sameArray = [NSMutableArray array];
        
        // 互斥锁(Mutex):是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。
        NSLock *lock = [[NSLock alloc] init];
        
        // 判断依据的字符串
        NSString *same = @"8";
        
        for (int i = 0; i < 1000; i++) {
            NSString *value = [NSString stringWithFormat:@"%d",getScopeInsideRandomValue(0, 9999)];
            [array addObject:value];
        }
        // 4个线程
        size_t taskCount = 4;
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_apply(taskCount, queue, ^(size_t index){
            
            NSLog(@"current task %@",[NSThread currentThread]);
            NSRange range = NSMakeRange(index * (array.count / 4), array.count / 4);
            NSLog(@"range = %@",NSStringFromRange(range));
            NSArray *currentArray = [array subarrayWithRange:range];
            for (int i = 0; i < currentArray.count; i++) {
                NSString *value = [currentArray objectAtIndex:i];
                if ([value containsString:same]) {
                    [lock lock];
                    [sameArray addObject:value];
                    [lock unlock];
                }
            }
        });
        
        // 排序
        sameArray = [[sameArray sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
            return [obj1 intValue] > [obj2 intValue];
        }] mutableCopy];
        
        NSLog(@"%@",sameArray);

打印结果

GCD[6098:544023] current task <NSThread: 0x10053d2b0>{number = 4, name = (null)}
GCD[6098:544021] current task <NSThread: 0x10053bb80>{number = 3, name = (null)}
GCD[6098:544022] current task <NSThread: 0x1004007a0>{number = 2, name = (null)}
GCD[6098:544023] range = {250, 250}
GCD[6098:544022] range = {500, 250}
GCD[6098:543914] current task <NSThread: 0x100603f00>{number = 1, name = main}
GCD[6098:544021] range = {750, 250}
GCD[6098:543914] range = {0, 250}
GCD[6098:543914] (
    18,
    81,
    188,
    238,
    285,
    387,
    ...
    ...
    ...
)

从打印的结果还可以看出 dispatch_apply 会使用主线程进行处理业务。
如果不想阻塞到主线程,可以把 apply 放在异步线程中。

        dispatch_async(queue, ^{
            dispatch_apply(taskCount, queue, ^(size_t index){
                NSLog(@"current task %@",[NSThread currentThread]);
                // code ...
            });
        });

虽然说 GCD 轻量且低负载,但是将 block 提交至 queue 还是很消耗资源的—— block 需要被拷贝和入队,同时适当的工作线程需要被通知。过度的创建线程,比如几十个,并不能带来更高的运行效率,反而会浪费资源。

如有错误,还望纠正!

上一篇 下一篇

猜你喜欢

热点阅读