iOS 多线程技术 GCD

1、介绍

  1. 什么是GCD?
    Grand Central Dispatch,是苹果公司开发的一套多核编程的底层API。GCD首次发布在Mac OS X 10.6,iOS4及以上也可用。GCD存在于libdispatch.dylib这个库中,iOS程序默认动态加载这个库,无需手动引入。
  2. GCD工作原理
    让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。一个任务可以是一个Function或是一个block。GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节。
  3. GCD优势
    GCD会自动利用更多的CPU内核(比如双核、四核)。
    GCD会自动将队列中的任务取出,放到对应的线程中执行,任务的取出遵循队列的FIFO原则。
  4. GCD核心概念
    任务(block)和队列(queue)。

2、队列(Dispatch Queue)

  1. GCD队列可以分为两大类型:串行队列和并发队列。
    串行队列(Serial Dispatch Queue):同时只执行一个任务,通常用于同步访问特定的资源或数据。当你创建多个串行队列时,虽然它们各自是同步执行的,但队列之间是并发执行的。
    并发队列(Concurrent Dispatch Queue):可以让多个任务并发执行(自动开启多个线程同时执行任务,如果同时执行10个任务,那么10个任务并不是开启10个线程,线程会根据任务执行情况复用,具体线程数由系统决定),并发功能只有在异步(dispatch_async)函数下才有效,执行完成的顺序是随机的。
  2. GCD两种获取队列的方式:手动创建和获取系统提供的。

(1)dispatch_queue_create(手动创建队列)
【语法】
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

示例

- (void)dispatchQueueCreateTest
 {
     NSLog(@"A:%@", [NSThread currentThread]);
     dispatch_queue_t queue = dispatch_queue_create("com.jinnchang.gcd", DISPATCH_QUEUE_SERIAL);
     dispatch_async(queue, ^{
         NSLog(@"B:%@", [NSThread currentThread]);
     });
     dispatch_async(queue, ^{
         NSLog(@"C:%@", [NSThread currentThread]);
     });
     NSLog(@"D:%@", [NSThread currentThread]);
 }
 // 打印结果:
 // 2021-05-09 13:59:26.984 GCDDemo[3714:99120] A:{number = 1, name = main}
 // 2021-05-09 13:59:26.984 GCDDemo[3714:99120] D:{number = 1, name = main}
 // 2021-05-09 13:59:26.984 GCDDemo[3714:99215] B:{number = 2, name = (null)}
 // 2021-05-09 13:59:26.985 GCDDemo[3714:99215] C:{number = 2, name = (null)}

【说明】
dispatch_queue_create参数中的label是队列名称,一般使用倒序的全域名(虽然可以不给队列指定一个名称,但是有名称的队列可以让我们在遇到问题时更好调试)。attr为DISPATCH_QUEUE_SERIAL时返回串行队列,为DISPATCH_QUEUE_CONCURRENT时返回并发队列(如果填NULL默认是DISPATCH_QUEUE_SERIAL)。

(2)dispatch_get_main_queue(获取系统主队列)、dispatch_get_global_queue(获取系统全局并发队列)
【语法】
dispatch_queue_t dispatch_get_main_queue(void);
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

示例

- (void)mainAndGlobalQueueTest
 {
     NSLog(@"A:%@", [NSThread currentThread]);
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
         // 耗时操作
         NSLog(@"B:%@", [NSThread currentThread]);
         dispatch_async(dispatch_get_main_queue(), ^{
             // 更新界面操作
            NSLog(@"C:%@", [NSThread currentThread]);
         });
     });
     NSLog(@"D:%@", [NSThread currentThread]);
 }
 // 打印结果:
 // 2021-05-09 14:05:13.732 GCDDemo[3794:101596] A:{number = 1, name = main}
 // 2021-05-09 14:05:13.733 GCDDemo[3794:101596] D:{number = 1, name = main}
 // 2021-05-09 14:05:13.733 GCDDemo[3794:101653] B:{number = 2, name = (null)}
 // 2021-05-09 14:05:13.764 GCDDemo[3794:101596] C:{number = 1, name = main}

【说明】
主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行。主线程是唯一可用于更新UI的线程。
GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建。
dispatch_get_global_queue参数中的identifier是优先级,有如下四种;flags这个参数是留给以后用的,暂时用不上,传0即可。

#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台

3、调度任务

1、dispatch_async(用异步的方式执行任务)
【语法】
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

示例

- (void)dispatchAsyncTest
 {
     NSLog(@"A:%@", [NSThread currentThread]);
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     dispatch_async(queue, ^{
         NSLog(@"B:%@", [NSThread currentThread]);
     });
     dispatch_async(queue, ^{
         NSLog(@"C:%@", [NSThread currentThread]);
     });
     NSLog(@"D:%@", [NSThread currentThread]);
 }
 // 打印结果:
 // 2021-05-09 14:18:43.040 GCDDemo[3965:106717] A:{number = 1, name = main}
 // 2021-05-09 14:18:43.041 GCDDemo[3965:106717] D:{number = 1, name = main}
 // 2021-05-09 14:18:43.041 GCDDemo[3965:106754] B:{number = 2, name = (null)}
 // 2021-05-09 14:18:43.041 GCDDemo[3965:106755] C:{number = 3, name = (null)}

【说明】
异步方式具备开启新线程的能力。会立即返回,不会堵塞当前线程。

2、dispatch_sync(用同步方式执行任务)
【语法】
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

示例

- (void)dispatchSyncTest
 {
     NSLog(@"A:%@", [NSThread currentThread]);
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     dispatch_sync(queue, ^{
         NSLog(@"B:%@", [NSThread currentThread]);
     });
     dispatch_sync(queue, ^{
         NSLog(@"C:%@", [NSThread currentThread]);
     });
     NSLog(@"D:%@", [NSThread currentThread]);
 }
 // 打印结果:
 // 2021-05-09  14:22:38.085 GCDDemo[4032:108410] A:{number = 1, name = main}
 // 2021-05-09  14:22:38.085 GCDDemo[4032:108410] B:{number = 1, name = main}
 // 2021-05-09  14:22:38.086 GCDDemo[4032:108410] C:{number = 1, name = main}
 // 2021-05-09  14:22:38.086 GCDDemo[4032:108410] D:{number = 1, name = main}

【说明】
同步方式不具备开启新线程的能力。在堵塞在当前线程中,等block执行完成再返回。

3、dispatch_after(让队列任务延时执行)
【语法】
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);

示例

- (void)dispatchAfterTest
 {
     NSLog(@"A:%@", [NSThread currentThread]);
     dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC));
     dispatch_after(time, dispatch_get_main_queue(), ^{
         NSLog(@"B:%@", [NSThread currentThread]);
     });
     NSLog(@"C:%@", [NSThread currentThread]);
 }
 // 打印结果:
 // 2021-05-09 14:29:01.119 GCDDemo[4198:111631] A:{number = 1, name = main}
 // 2021-05-09 14:29:01.120 GCDDemo[4198:111631] C:{number = 1, name = main}
 // 2021-05-09 14:29:02.185 GCDDemo[4198:111631] B:{number = 1, name = main}

【说明】
dispatch_after的真正含义是在设定的时间后把任务添加进队列中,并不是表示在设定的时间后执行,大部分情况该函数能达到我们的预期,只有在对时间要求非常精准的情况下才可能会出现问题,这种情况下就要慎重使用了。
NSEC_PER_SEC表示的是秒数,它还提供了NSEC_PER_MSEC表示毫秒数。

4、dispatch_apply
【语法】
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

示例

- (void)dispatchApplyTest
 {
     NSLog(@"A:%@", [NSThread currentThread]);
     NSArray *array = @[@"B",@"C",@"D"];
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     dispatch_apply([array count], queue, ^(size_t index) {
         NSLog(@"%@:%@", [array objectAtIndex:index], [NSThread currentThread]);
     });
     NSLog(@"E:%@", [NSThread currentThread]);
 }
 // 打印结果:
 // 2021-05-09  14:33:34.729 GCDDemo[4306:113699] A:{number = 1, name = main}
 // 2021-05-09  14:33:34.729 GCDDemo[4306:113721] C:{number = 3, name = (null)}
 // 2021-05-09  14:33:34.729 GCDDemo[4306:113699] B:{number = 1, name = main}
 // 2021-05-09  14:33:34.729 GCDDemo[4306:113722] D:{number = 2, name = (null)}
 // 2021-05-09  14:33:34.730 GCDDemo[4306:113699] E:{number = 1, name = main}

【说明】
如果要对某个数组中的所有元素执行同样的block的时候,这个函数就显得很有用了。并行运算,然后等待所有运算结束。由于是并发队列,不能保证哪个索引元素先执行完,但是dispatch_apply函数是同步的,执行过程中会使线程在此处等待。如果需要整个操作异步执行,在dispatch_apply外再套一层dispatch_async可以实现。

5、dispatch_once
【语法】
void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);

示例

+ (MyManager *)sharedInstance
 {
     static MyManager *sharedManager;
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
         sharedManager = [[MyManager alloc] init];
     });
     return sharedManager;
 }

【说明】
dispatch_once通常用在单例模式上,保证在程序运行期间block只执行一次。

6、dispatch_group_async
【语法】
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);

示例

- (void)dispatchGroupAsyncTest
 {
     NSLog(@"A:%@", [NSThread currentThread]);
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     dispatch_group_t group = dispatch_group_create();
     dispatch_group_async(group, queue, ^{
         NSLog(@"B:%@", [NSThread currentThread]);
     });
     dispatch_group_async(group, queue, ^{
         NSLog(@"C:%@", [NSThread currentThread]);
     });
     dispatch_group_notify(group, dispatch_get_main_queue(), ^{
         NSLog(@"D:%@", [NSThread currentThread]);
     });
     NSLog(@"E:%@", [NSThread currentThread]);
 }
 // 打印结果:
 // 2021-05-09 14:42:37.609 GCDDemo[4462:117434] A:{number = 1, name = main}
 // 2021-05-09 14:42:37.610 GCDDemo[4462:117434] E:{number = 1, name = main}
 // 2021-05-09 14:42:37.610 GCDDemo[4462:117480] B:{number = 3, name = (null)}
 // 2021-05-09 14:42:37.610 GCDDemo[4462:117481] C:{number = 2, name = (null)}
 // 2021-05-09 14:42:37.642 GCDDemo[4462:117434] D:{number = 1, name = main}

【说明】
dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。除了使用dispatch_group_notify函数可以得到最后执行完的通知外,还可以使用dispatch_group_wait。需要注意的是,dispatch_group_wait实际上会使当前的线程处于等待的状态,也就是说如果是在主线程执行dispatch_group_wait,在Block执行完之前,主线程会处于卡死的状态。dispatch_group_wait的第二个参数是指定超时时间,如果指定为DISPATCH_TIME_FOREVER则表示会永久等待,直到Block全部执行完。

7、dispatch_barrier_async
【语法】
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

示例

- (void)dispatchBarrierAsyncTest
 {
     NSLog(@"A:%@", [NSThread currentThread]);
     dispatch_queue_t queue = dispatch_queue_create("com.jinnchang.gcd", DISPATCH_QUEUE_CONCURRENT);
     dispatch_async(queue, ^{
         NSLog(@"B:%@", [NSThread currentThread]);
     });
     dispatch_async(queue, ^{
         NSLog(@"C:%@", [NSThread currentThread]);
     });
     dispatch_barrier_async(queue, ^{
         NSLog(@"D:%@", [NSThread currentThread]);
     });
     dispatch_async(queue, ^{
         NSLog(@"E:%@", [NSThread currentThread]);
     });
     dispatch_async(queue, ^{
         NSLog(@"F:%@", [NSThread currentThread]);
     });
     NSLog(@"G:%@", [NSThread currentThread]);
 }
 // 打印结果:
 // 2021-05-09 14:46:30.992 GCDDemo[4554:119327] A:{number = 1, name = main}
 // 2021-05-09 14:46:30.993 GCDDemo[4554:119327] G:{number = 1, name = main}
 // 2021-05-09 14:46:30.993 GCDDemo[4554:119367] C:{number = 2, name = (null)}
 // 2021-05-09 14:46:30.993 GCDDemo[4554:119366] B:{number = 3, name = (null)}
 // 2021-05-09 14:46:30.993 GCDDemo[4554:119366] D:{number = 3, name = (null)}
 // 2021-05-09 14:46:30.993 GCDDemo[4554:119367] F:{number = 2, name = (null)}
 // 2021-05-09 14:46:30.993 GCDDemo[4554:119366] E:{number = 3, name = (null)}

【说明】
dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行.
如果上述示例中dispatch_queue_t是使用dispatch_get_global_queue创建的会发现执行顺序与预想的不一致,原因是dispatch_barrier_async的顺序执行仍然依赖queue的类型,必须要求是dispatch_queue_create创建的,而且attr参数值必须是DISPATCH_QUEUE_CONCURRENT。

4、管理调度对象

  1. dispatch_suspend、dispatch_resume
    【语法】
    void dispatch_suspend(dispatch_object_t object);
    void dispatch_resume(dispatch_object_t object);
    【说明】
    dispatch_suspend(暂停)和dispatch_resume(恢复)在主线程上不起作用。
    dispatch_suspend并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行。

  2. dispatch_release、dispatch_retain
    在MRC中需要释放手动创建的队列,在ARC中系统自动释放。通过dispatch_get_main_queue和dispatch_get_global_queue获取的系统全局队列,不用retain或release。

5、信号量

【语法】
dispatch_semaphore_t dispatch_semaphore_create(long value);
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);

示例

- (void)dispatchSemaphoreTest
 {
     NSLog(@"A:%@", [NSThread currentThread]);
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
     NSArray *array = @[@"B", @"C", @"D", @"E"];
     for (int i = 0; i < [array count]; i++) {
         dispatch_async(queue, ^{
             dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
             [NSThread sleepForTimeInterval:1];
             NSLog(@"%@:%@", [array objectAtIndex:i], [NSThread currentThread]);
             dispatch_semaphore_signal(semaphore);
         });
     }
     NSLog(@"F:%@", [NSThread currentThread]);
 }
 // 打印结果:
 // 2021-05-09 14:50:54.840 GCDDemo[4675:121679] A:{number = 1, name = main}
 // 2021-05-09 14:50:54.840 GCDDemo[4675:121679] F:{number = 1, name = main}
 // 2021-05-09 14:50:55.842 GCDDemo[4675:121712] B:{number = 2, name = (null)}
 // 2021-05-09 14:50:56.843 GCDDemo[4675:121711] C:{number = 3, name = (null)}
 // 2021-05-09 14:50:57.845 GCDDemo[4675:121713] D:{number = 4, name = (null)}
 // 2021-05-09 14:50:58.846 GCDDemo[4675:121718] E:{number = 5, name = (null)}

【说明】
信号量在多线程开发中被广泛使用,当一个线程在进入一段关键代码之前,线程必须获取一个信号量,一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待前面的线程释放信号量。
信号量的具体做法是:当信号计数大于0时,每条进来的线程使计数减1,直到变为0,变为0后其他的线程将进不来,处于等待状态;执行完任务的线程释放信号,使计数加1,如此循环下去。另外dispatch_semaphore_wait同样也支持超时,只需要给其第二个参数指定超时的时候,然后通过返回值来判断。

信号量实现原理详细解释

6、相关概念解析

1、死锁
【示例】

- (void)deadLockTest
 {
     NSLog(@"A:%@", [NSThread currentThread]);
     dispatch_sync(dispatch_get_main_queue(), ^{
         NSLog(@"B:%@", [NSThread currentThread]);
     });
     NSLog(@"C:%@", [NSThread currentThread]);
 }

【说明】
上述代码会发生死锁,因为主线程通过dispatch_sync把block交给主队列后,会等待block里的任务结束再往下走自身的任务,而队列是先进先出的,block里的任务也在等待主队列当中排在它之前的任务都执行完了再走自己,这种循环等待就形成了死锁。所以在主线程当中使用dispatch_sync将任务加到主队列是不可取的。

你可能感兴趣的:(iOS 多线程技术 GCD)