一、介绍
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原则。
GCD会自己主动管理线程的生命周期(创建线程、调度任务、销毁线程)。
4、GCD核心概念
任务(block)和队列(queue)。
二、队列(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]);
}
// 打印结果:
// 2015-08-17 13:59:26.984 GCDDemo[3714:99120] A:<NSThread: 0x7fa3d052c9c0>{number = 1, name = main}
// 2015-08-17 13:59:26.984 GCDDemo[3714:99120] D:<NSThread: 0x7fa3d052c9c0>{number = 1, name = main}
// 2015-08-17 13:59:26.984 GCDDemo[3714:99215] B:<NSThread: 0x7fa3d2804300>{number = 2, name = (null)}
// 2015-08-17 13:59:26.985 GCDDemo[3714:99215] C:<NSThread: 0x7fa3d2804300>{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]);
}
// 打印结果:
// 2015-08-17 14:05:13.732 GCDDemo[3794:101596] A:<NSThread: 0x7fd5ab4055c0>{number = 1, name = main}
// 2015-08-17 14:05:13.733 GCDDemo[3794:101596] D:<NSThread: 0x7fd5ab4055c0>{number = 1, name = main}
// 2015-08-17 14:05:13.733 GCDDemo[3794:101653] B:<NSThread: 0x7fd5ada01350>{number = 2, name = (null)}
// 2015-08-17 14:05:13.764 GCDDemo[3794:101596] C:<NSThread: 0x7fd5ab4055c0>{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 // 后台
三、调度任务
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]);
}
// 打印结果:
// 2015-08-17 14:18:43.040 GCDDemo[3965:106717] A:<NSThread: 0x7f994842d760>{number = 1, name = main}
// 2015-08-17 14:18:43.041 GCDDemo[3965:106717] D:<NSThread: 0x7f994842d760>{number = 1, name = main}
// 2015-08-17 14:18:43.041 GCDDemo[3965:106754] B:<NSThread: 0x7f9948430970>{number = 2, name = (null)}
// 2015-08-17 14:18:43.041 GCDDemo[3965:106755] C:<NSThread: 0x7f9948700980>{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]);
}
// 打印结果:
// 2015-08-17 14:22:38.085 GCDDemo[4032:108410] A:<NSThread: 0x7feca852c920>{number = 1, name = main}
// 2015-08-17 14:22:38.085 GCDDemo[4032:108410] B:<NSThread: 0x7feca852c920>{number = 1, name = main}
// 2015-08-17 14:22:38.086 GCDDemo[4032:108410] C:<NSThread: 0x7feca852c920>{number = 1, name = main}
// 2015-08-17 14:22:38.086 GCDDemo[4032:108410] D:<NSThread: 0x7feca852c920>{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]);
}
// 打印结果:
// 2015-08-17 14:29:01.119 GCDDemo[4198:111631] A:<NSThread: 0x7fe131d2d6d0>{number = 1, name = main}
// 2015-08-17 14:29:01.120 GCDDemo[4198:111631] C:<NSThread: 0x7fe131d2d6d0>{number = 1, name = main}
// 2015-08-17 14:29:02.185 GCDDemo[4198:111631] B:<NSThread: 0x7fe131d2d6d0>{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]);
}
// 打印结果:
// 2015-08-17 14:33:34.729 GCDDemo[4306:113699] A:<NSThread: 0x7fc35942d820>{number = 1, name = main}
// 2015-08-17 14:33:34.729 GCDDemo[4306:113721] C:<NSThread: 0x7fc359615a40>{number = 3, name = (null)}
// 2015-08-17 14:33:34.729 GCDDemo[4306:113699] B:<NSThread: 0x7fc35942d820>{number = 1, name = main}
// 2015-08-17 14:33:34.729 GCDDemo[4306:113722] D:<NSThread: 0x7fc35b063b60>{number = 2, name = (null)}
// 2015-08-17 14:33:34.730 GCDDemo[4306:113699] E:<NSThread: 0x7fc35942d820>{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]);
}
// 打印结果:
// 2015-08-17 14:42:37.609 GCDDemo[4462:117434] A:<NSThread: 0x7faeb952c900>{number = 1, name = main}
// 2015-08-17 14:42:37.610 GCDDemo[4462:117434] E:<NSThread: 0x7faeb952c900>{number = 1, name = main}
// 2015-08-17 14:42:37.610 GCDDemo[4462:117480] B:<NSThread: 0x7faeb9703380>{number = 3, name = (null)}
// 2015-08-17 14:42:37.610 GCDDemo[4462:117481] C:<NSThread: 0x7faebc300000>{number = 2, name = (null)}
// 2015-08-17 14:42:37.642 GCDDemo[4462:117434] D:<NSThread: 0x7faeb952c900>{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]);
}
// 打印结果:
// 2015-08-17 14:46:30.992 GCDDemo[4554:119327] A:<NSThread: 0x7f9a9142d760>{number = 1, name = main}
// 2015-08-17 14:46:30.993 GCDDemo[4554:119327] G:<NSThread: 0x7f9a9142d760>{number = 1, name = main}
// 2015-08-17 14:46:30.993 GCDDemo[4554:119367] C:<NSThread: 0x7f9a93109030>{number = 2, name = (null)}
// 2015-08-17 14:46:30.993 GCDDemo[4554:119366] B:<NSThread: 0x7f9a91500000>{number = 3, name = (null)}
// 2015-08-17 14:46:30.993 GCDDemo[4554:119366] D:<NSThread: 0x7f9a91500000>{number = 3, name = (null)}
// 2015-08-17 14:46:30.993 GCDDemo[4554:119367] F:<NSThread: 0x7f9a93109030>{number = 2, name = (null)}
// 2015-08-17 14:46:30.993 GCDDemo[4554:119366] E:<NSThread: 0x7f9a91500000>{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。
四、管理调度对象
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。
五、信号量
【语法】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]);
}
// 打印结果:
// 2015-08-17 14:50:54.840 GCDDemo[4675:121679] A:<NSThread: 0x7fbb92c1ea70>{number = 1, name = main}
// 2015-08-17 14:50:54.840 GCDDemo[4675:121679] F:<NSThread: 0x7fbb92c1ea70>{number = 1, name = main}
// 2015-08-17 14:50:55.842 GCDDemo[4675:121712] B:<NSThread: 0x7fbb94a01140>{number = 2, name = (null)}
// 2015-08-17 14:50:56.843 GCDDemo[4675:121711] C:<NSThread: 0x7fbb94a0d320>{number = 3, name = (null)}
// 2015-08-17 14:50:57.845 GCDDemo[4675:121713] D:<NSThread: 0x7fbb92e53750>{number = 4, name = (null)}
// 2015-08-17 14:50:58.846 GCDDemo[4675:121718] E:<NSThread: 0x7fbb94a0c510>{number = 5, name = (null)}
【说明】
信号量在多线程开发中被广泛使用。当一个线程在进入一段关键代码之前,线程必须获取一个信号量,一旦该关键代码段完毕了,那么该线程必须释放信号量。
其他想进入该关键代码段的线程必须等待前面的线程释放信号量。
信号量的详细做法是:当信号计数大于0时。每条进来的线程使计数减1,直到变为0,变为0后其它的线程将进不来,处于等待状态。运行完任务的线程释放信号。使计数加1,如此循环下去。另外dispatch_semaphore_wait相同也支持超时,仅仅须要给其第二个參数指定超时的时候,然后通过返回值来推断。
六、相关概念解析
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将任务加到主队列是不可取的。