GCD是一种易用、高效的多线程技术,可代替NSThread和performSelector使用,其性能高于NSThread。
创建/获取
GCD可创建有序执行的线程(串行线程)和无序线程(并行)两种。全局线程是所有应用程序共用的并行线程,实效性相对较差。串行线程会等待前一个线程执行完成再执行另一个线程。
dispatch_queue_t queue = dispatch_queue_create("MY_THREAD", NULL); // 串行线程
dispatch_queue_t queue = dispatch_queue_create("MY_THREAD", DISPATCH_QUEUE_CONCURRENT); // 并行线程
dispatch_queue_t queue = dispatch_get_main_queue(); // 主线程
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 全局线程(并行线程)
全局线程由于是所有应用程序公用的,因此它具有优先级参数。默认情况下使用_create方式创建的线程具有相同的优先级,如果需要改变某个线程的优先级,可采用如下方式:
dispatch_queue_t queue1 = dispatch_queue_create("MY_GCD", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_set_target_queue(queue1, global);
// queue1的优先级被设置成跟global一样。
iOS6以下系统即使开启了ARC,在使用_create API创建线程后仍需要手动使用dispatch_release来释放线程。
dispatch_queue_t queue = dispatch_queue_create("MY_THREAD", NULL);
dispatch_async(queue, ^{ DO SOMETHING });
dispatch_release(queue);
第三行直接release是没问题的,不用担心block中的任务还没执行完。因为按照dispatch_async以及block的实现逻辑,线程会被复制到堆上并被block持有生成一个autorelease对象。
执行
dispatch_after
此API并不是指『立即将任务加入指定线程并在指定时间后执行任务』,而是『在指定时间后将任务加入指定线程』。由于受到线程状态等因素影响,任务实际执行时间是有延迟的。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1*NSEC_PER_SEC)), queue, ^{});
// 线程的实际执行时间应该在1秒到1+1/60秒之间,甚至更晚。
dispatch_async
异步执行,不会阻塞当前线程。
dispatch_sync
同步执行,使用此API要特别小心,如果传入的线程对象是『当前线程』,极有可能会引起线程死锁。
// 在主线程开启了sync方式,并传入主线程,必然死锁。
dispatch_sync(dispatch_get_main_queue(), ^{});
DISPATCH_GROUP
如果需要在N个任务执行完成之后,执行另一个任务M,除了使用串行线程外,还可以使用dispatch_group群组。
dispatch_queue_t queue1 = dispatch_queue_create("MY_GCD", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue1, ^{NSLog(@"1");});
dispatch_group_async(group, queue1, ^{NSLog(@"2");});
dispatch_group_async(group, queue1, ^{NSLog(@"3");});
dispatch_group_notify(group, queue1, ^{NSLog(@"Finish");});
如果想给群组设置一个超时时间,可使用dispatch_group_wait API。此API会返回在指定时间后尚未执行结束的任务数量,如果数量等于0即代表任务已全部完成。
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1*NSEC_PER_SEC));
// 在1秒后时间后继续执行接下去的代码
DISPATCH_APPLY
APPLY API用于执行指定次数的线程任务,即便传入的线程类型是并行线程,此API也会等待所有任务都执行完成之后再继续执行后续代码。
一个应用场景是处理数组中每一个元素,假设每个元素都是独立的,产生的结果不会互相影响,使用此API的效率会比for循环高。for循环可以认为是串行方式。
dispatch_queue_t queue1 = dispatch_queue_create("MY_GCD", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray * array = [NSMutableArray new];
dispatch_apply(array.count, queue1, ^(size_t index) {
NSLog(@"%zu:%@",index, array[index]);
});
// APPLY结束后会继续执行接下去的代码
数据竞争
多线程必然遇到数据竞争的问题,即不同的任务对同一个资源进行了更新,由于执行时间不定可能导致结果不正确的问题。
对同一个资源除了考虑使用串行线程以外,也可以使用并行线程。使用并行线程时通过dispatch_barrier_async方法来执行『写』这个操作。
dispatch_queue_t queue1 = dispatch_queue_create("MY_GCD", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queuq1, blk_read1);
dispatch_async(queuq1, blk_read2);
dispatch_barrier_async(queue1, blk_write1);
dispatch_async(queuq1, blk_read3);
dispatch_barrier_async(queue1, blk_write2);
dispatch_async(queuq1, blk_read4);
write1任务会等待blk_read1、blk_read2任务执行后再执行,并且等待write1任务执行完成后才会执行read3任务。在read3执行后才会执行write2任务,在write2任务执行成功后才会执行read4。