iOS 多线程之GCD

2020-12-23  本文已影响0人  星星1024

1 GCD简述

Apple源码--Dispatch

Grand Central Dispatch(GCD)Apple开发的一个多核编程的较新的解决方法.它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统.它是一个在线程池模式的基础上执行的并发任务.在Mac OS X 10.6雪豹中首次推出,也可在iOS 4及以上版本使用.

GCD优点

2 GCD任务和队列

任务

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

任务的创建分为:同步任务dispatch_sync和异步任务dispatch_async

dispatch_sync(queue, ^{
    // 同步执行任务
    // code snippet
});
dispatch_async(queue, ^{
    // 异步执行任务
    // code snippet
});

队列(Dispatch Queue)

队列:队列指执行任务的等待队列,即用来存放任务的队列.队列是一种特殊的线性表,采用FIFIO(先进先出)的原则.

image

GCD队列分为两种:串行队列并行队列
主要区别: 执行顺序不同,以及开启线程数不同.

注意:<u>并发队列 的并发功能只有在异步(dispatch_async)方法下才有效.其他线程下,串行执行任务</u>

image

队列的创建:dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);

// 串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_SERIAL);
// 并发队列
dispatch_queue_t concurrentlQueue = dispatch_queue_create("com.appleid.functionB", DISPATCH_QUEUE_CONCURRENT);
// 主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 全局并发队列 (参数0: 填写默认 , 参数1: 填写0)
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

3 队列和同步,异步任务组合

区别 并发队列 串行队列 主队列
同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 死锁卡住不执行
异步(async) 有开启新线程,并发执行任务 有开启新线程(1条),串行执行任务 没有开启新线程,串行执行任务

4 GCD方法

4.1 dispatch_after:延时执行方法

主要:<u>dispatch_after方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中.准确来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after 方法是很有效的</u>

- (void)after {
    NSLog(@"当前线程%@", [NSThread currentThread]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"线程after:%@", [NSThread currentThread]);  // 打印当前线程
    });
}

4.2 dispatch_once:只执行一次

在创建单例、或者有整个程序运行过程中只执行一次的代码时,就可以使用dispatch_once方法.dispatch_once方法能保证某段代码在程序运行过程中只被执行1次,并且即使在多线程的环境下, dispatch_once也可以保证线程安全.

- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行一次, 默认线程安全
        // code snippet
    });
}

4.3 dispatch_barrier_async: 栅栏函数

Apple官方文档

对这个函数的调用总是在block被提交之后立即返回,并且从不等block待被调用.当barrier block到达私有并发队列的前端时,它不会立即执行.相反,队列将等待,直到当前执行的块完成执行.此时,barrier block自己执行.在barrier block之后提交的任何block都不会执行,直到barrier block完成.
您指定的队列应该是您自己使用dispatch_queue_create函数创建的并发队列.如果传递给此函数的队列是一个串行队列或一个全局并发队列,则此函数的行为与dispatch_async函数类似.

image

- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任务2, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3, %@", [NSThread currentThread]);
    });

    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:2- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任务2, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3, %@", [NSThread currentThread]);
    });

    
    dispatch_barrier_sync(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务4 barrier, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务5, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务6, %@", [NSThread currentThread]);
    });
    
    NSLog(@"任务7, %@", [NSThread currentThread]);
}];
        NSLog(@"barrier任务4, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务5, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务6, %@", [NSThread currentThread]);
    });
}

执行结果:

任务2, <NSThread: 0x139040570>{number = 4, name = (null)}
任务3, <NSThread: 0x139458a90>{number = 6, name = (null)}
任务1, <NSThread: 0x139043ce0>{number = 5, name = (null)}
任务4 barrier, <NSThread: 0x137e0b8d0>{number = 1, name = main}
任务7, <NSThread: 0x137e0b8d0>{number = 1, name = main}
任务5, <NSThread: 0x139043ce0>{number = 5, name = (null)}
任务6, <NSThread: 0x139458a90>{number = 6, name = (null)}
- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任务2, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3, %@", [NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务4 barrier, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务5, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务6, %@", [NSThread currentThread]);
    });

    NSLog(@"任务7, %@", [NSThread currentThread]);
}

执行结果:

任务7, <NSThread: 0x10360aea0>{number = 1, name = main}
任务2, <NSThread: 0x1035a4f90>{number = 3, name = (null)}
任务1, <NSThread: 0x105e79130>{number = 6, name = (null)}
任务3, <NSThread: 0x1036afae0>{number = 5, name = (null)}
任务4 barrier, <NSThread: 0x1036afae0>{number = 5, name = (null)}
任务5, <NSThread: 0x1036afae0>{number = 5, name = (null)}
任务6, <NSThread: 0x105e79130>{number = 6, name = (null)}

4.4 dispatch_apply:快速迭代(高效for循环)

Apple官方文档

此函数将多个调用的block提交给调度队列,并等待任务block的所有迭代完成后再返回.如果目标队列是由dispatch_get_global_queue返回的并发队列,则可以并发调用该block,因此它必须是reentrant安全的.在并发队列中使用此函数可以作为一种有效的并行for循环.
迭代的当前索引被传递给block的每次调用.

- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // dispatch_apply是同步的
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"同步index:%zu %@", index, [NSThread currentThread]);
    });

    // 如果想异步,包装一层
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_apply(10, queue, ^(size_t index) {
            NSLog(@"异步index:%zu %@", index, [NSThread currentThread]);
        });
    });
}

4.5 dispatch_group: 队列组

- (void)group {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [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任务完成");
    });
}

执行结果:

任务2, <NSThread: 0x281f87280>{number = 5, name = (null)}
任务1, <NSThread: 0x281fa0c00>{number = 8, name = (null)}
任务3, <NSThread: 0x281fc4d80>{number = 1, name = main}
group任务完成
- (void)group {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务2, %@", [NSThread currentThread]);
    });

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group任务完成");
 }

执行结果:

任务2, <NSThread: 0x2817f9540>{number = 4, name = (null)}
任务1, <NSThread: 0x2817ff9c0>{number = 6, name = (null)}
group任务完成
- (void)group1 {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务2, %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3, %@", [NSThread currentThread]);
        NSLog(@"group任务完成");
    });
}

执行结果:

任务2, <NSThread: 0x281df3e40>{number = 4, name = (null)}
任务1, <NSThread: 0x281de3f80>{number = 7, name = (null)}
任务3, <NSThread: 0x281db0d80>{number = 1, name = main}
group任务完成

4.6 dispatch_semaphore: 信号量

dispatch_semaphoreGCD中的信号量,持有计数的信号, dispatch Semaphore中,使用计数来完成这个功能,计数小于0时等待,不可通过.计数为0或大于0时可通过.

主要使用:

dispatch_semaphore三个方法:

- (void)semaphor {
    
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);

        dispatch_semaphore_signal(semaphore);
    });
    NSLog(@"当前线程1:%@", [NSThread currentThread]);

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"任务完成");

}

执行结果:

当前线程:<NSThread: 0x282784d80>{number = 1, name = main}
当前线程1:<NSThread: 0x282784d80>{number = 1, name = main}
任务1, <NSThread: 0x2827d6cc0>{number = 6, name = (null)}
任务完成

从打印结果可知执行流程为:

完整代码见GitHub->多线程(附大厂面试讲解)


如有不足之处,欢迎予以指正, 如果感觉写的不错,记得给个赞呦!

上一篇下一篇

猜你喜欢

热点阅读