什么是 GCD ?
GCD
全称为 Grand Central Dispatch
, 是一个异步执行任务的技术之一。 一般将应用程序中线程管理用的代码再系统级中实现。 开发者只需要定义想执行的任务并追加到适当的 Dispatch Queue
中, GCD
就能生成必要的线程并计划执行任务。 由于线程管理是作为系统的一部分来实现的, 因此可统一管理,也可执行任务,这样就比以前的线程更有效率。
相对于直接去写多线程的代码。通过 GCD
去使用多线程技术不仅非常简单,并且避免一些多线程操作中可能出现的问题。开发者要做的只是定义想执行的任务并追加到适当的 Dispatch Queue
中。
Dispatch Queue
Dispatch Queue 介绍
Dispatch Queue
是执行等待任务的队列。在使用 GCD
执行任务时,添加任务到对应的 Dispatch Queue
中。由 Dispatch Queue
控制怎么在线程上执行任务。Dispatch_Queue
按照任务追加的顺序执行(FIFO)。 Dispatch Queue
分类两种类型:
- Serial Dispatch Queue: 是一个串行队列,添加到这个队列中的任务会按照顺序一个任务执行完后才会执行下一个任务。
-
Concurrent Dispatch Queue: 是一个并行队列,添加到这个队列中的任务会多个任务同时执行。但是具体同时能执行多少个任务,这个
XNU 内核
去帮我们管理的。
创建 Dispatch Queue
获取上面两种 Dispatch Queue
可以通过 dispatch_queue_create
函数去创建。这个函数返回代表 Dispatch Queue
的 dispatch_queue_t
类型变量。
dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
// 其中第一个参数传入一个 C 字符串,作为名字来标识一个 Dispatch Queue ,这个名字在调试中非常有用,会显示在调试面板上。
// 第二个参数如果创建 Serial Dispatch Queue 则填入 NULL 。
// 如果需要创建 Concurrent Dispatch Queue 则填入DISPATCH_QUEUE_CONCURRENT。
获取系统提供的 Dispatch Queue
如果不自己创建 Dispatch Queue
,系统也开发者提供了Main Dispatch Queue
Global Dispatch Queue
。
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t globalQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t globalQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t globalQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t globalQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
// dispatch_get_main_queue 方法获得的是 Serial Dispatch Queue,主线程就是在这个 Dispatch Queue 内。
// dispatch_get_global_queue 方法获得都是 Concurrent Dispatch Queue。其第一个参数表示优先级,第二是保留字段(填0);
dispatch_async & dispatch_sync
dispatch_async & dispatch_sync 介绍
dispatch_async
和 dispatch_sync
这两个函数都可以把 Block任务
绑定到指定的 Dispatch Queue
中执行。区别从方法名中就可以看出。dispatch_async
是异步执行,dispatch_sync
是同步执行。看具体例子:
NSLog(@"1...");
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
sleep(1); NSLog(@"dispatch_sync");
});
NSLog(@"2...");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(1); NSLog(@"dispatch_async");
});
NSLog(@"3...");
// 上面这段代码执行的输出是:
2017-02-22 21:47:18.545 OCGCD[47679:28023198] 1...
2017-02-22 21:47:19.605 OCGCD[47679:28023198] dispatch_sync
2017-02-22 21:47:19.606 OCGCD[47679:28023198] 2...
2017-02-22 21:47:19.606 OCGCD[47679:28023198] 3...
2017-02-22 21:47:20.675 OCGCD[47679:28023256] dispatch_async
可以看出 dispatch_sync
这个函数会等到其 Block 内的任务执行完毕才会返回,然后继续往下执行。dispatch_async
则是直接返回继续往下执行,其 Block 内的任务会异步的去执行。
dispatch_async & dispatch_sync 和线程
上面说过 GCD
的背后是系统内核在管理线程,那 dispatch_async
和 dispatch_sync
执行相关了队列和线程那些关联呢? 看具体例子:
dispatch_queue_t serial = dispatch_queue_create("io.tao.serial", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrent = dispatch_queue_create("io.tao.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync (serial, ^{ NSLog(@"1: %@", [NSThread currentThread]); });
dispatch_async(serial, ^{ NSLog(@"2: %@", [NSThread currentThread]); });
dispatch_sync (concurrent, ^{ NSLog(@"3: %@", [NSThread currentThread]); });
dispatch_async(concurrent, ^{ NSLog(@"4: %@", [NSThread currentThread]); });
dispatch_async(mainQueue, ^{ NSLog(@"5: %@", [NSThread currentThread]); });
// 上面这段代码执行的输出是:
2017-02-22 22:29:04.783 OCGCD[50871:28074784] 1: <NSThread: 0x6080000646c0>{number = 1, name = main}
2017-02-22 22:29:04.783 OCGCD[50871:28074818] 2: <NSThread: 0x60000006ba40>{number = 3, name = (null)}
2017-02-22 22:29:04.783 OCGCD[50871:28074784] 3: <NSThread: 0x6080000646c0>{number = 1, name = main}
2017-02-22 22:29:04.784 OCGCD[50871:28074819] 4: <NSThread: 0x60000006b800>{number = 4, name = (null)}
2017-02-22 22:29:04.789 OCGCD[50871:28074784] 5: <NSThread: 0x6080000646c0>{number = 1, name = main}
由上面的例子得出,无论是 Serial Dispatch Queue
还是 Concurrent Dispatch Queue
通过 dispatch_sync
函数执行的都会在当前线程中执行任务。
通过 dispatch_async
函数执行的都会在子线程中执行任务。
但是有一个例外是 使用 dispatch_async
执行 mainQueue
队列也是在当前线程中执行的。永远都只会在主线程中执行。
dispatch_sync 和 死锁
细心的人可能已经发现,上面的例子没有出现下面这行代码:
dispatch_sync(mainQueue, ^{ NSLog(@"6: %@", [NSThread currentThread]); });
因为这行代码在主线程中执行时会产生死锁,程序会 Carsh 。产生死锁的原因是: 因为该代码在 Main Dispatch Queue(主线程)
中使用 dispatch_sync
在 Main Dispatch Queue(主线程)
上执行 Block 任务
, Main Dispatch Queue(主线程)
在一直等着 dispatch_sync
函数返回再继续执行,但是 dispatch_sync
又需要在 Main Dispatch Queue(主线程)
执行Block 任务
,所以这就像是一个死胡同,永远也等不到 dispatch_sync
函数返回。下面这个例子也是同样的问题:
dispatch_queue_t serial = dispatch_queue_create("io.tao.q1", DISPATCH_QUEUE_SERIAL);
dispatch_async(serial, ^{
dispatch_sync(serial, ^{ NSLog(@"Are you ok?"); });
});
所以在使用 dispatch_sync
函数去执行 Serial Dispatch Queue
队列时,要特别小心死锁的问题。在当前 Dispatch Queue
上去让 dispatch_queue
在当前 Dispatch Queue
执行任务时就会产生死锁。
dispatch_after
dispatch_after 介绍
dispatch_after
可以让 Block 任务
在指定时间后加入到 Dispatch Queue
中执行,并非在指定时间后执行 Block 任务
。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"dispatch_after");
});
// 上面的例子等同于 3 秒之后调用下面的代码
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"dispatch_after");
});
dispatch_time 介绍
上面的例子中 dispatch_after
第一个参数是指定时间 dispatch_time_t
类型的值。dispatch_time_t
函数定义如下:
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
// 第一个参数指定时间的开始, 一般使用 DISPATCH_NOTHROW 表示现在的时间。
// 第二个参数表示延后的时间,默认单位是纳秒, 可以 * NSEC_PER_SEC NSEC_PER_MSEC NSEC_PER_USEC 相关时间单位倍数来转换
dispatch_apply
dispatch_apply 介绍
dispatch_apply
可以多次执行一个队列里的 Block 任务,它会为 Block 任务提供一个计次的参数。 它和 dispatch_sync
一样,需要等到所有 Block 任务执行完,这个函数才会返回。 所以也要主要死锁的问题,当队列是 Serial Dispatch Queue
时,不要在当前 Dispatch Queue
上去让 dispatch_apply
在当前 Dispatch Queue
执行任务。
dispatch_apply(3, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"%zu", index);
});
NSLog(@"done");
// 上面的例子的输出,done 总是在最后输出。刚好可以验证上面的说法。dispatch_applay 是同步去执行的
2017-02-23 13:23:27.041 OCGCD[14310:28921393] 1
2017-02-23 13:23:27.042 OCGCD[14310:28921396] 2
2017-02-23 13:23:27.042 OCGCD[14310:28921344] 0
2017-02-23 13:23:27.043 OCGCD[14310:28921344] done
dispatch_set_target_queue
用其设置队列的优先级
上面有例子通过 dispatch_get_global_queue
去生成不同优先级的队列。通过 dispatch_queue_create
函数生成的 Dispatch Queue 的优先级都是默认的,如果要改变它们优先级可以通过 dispatch_set_target_queue
,看具体例子:
dispatch_queue_t queueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t queue = dispatch_queue_create("io.tao.serial", NULL);
dispatch_set_target_queue(queue, queueHigh);
// 上面的例子,把默认优先级的 queue, 设置成和 queueHigh 一样的高优先级
// 第一个参数填入要设置的 Dispatch Queue
// 第二个参数填入要参照的目标 Dispatch Queue
设置多个 Serial Dispatch Queue 的执行顺序
多个 Serial Dispatch Queue
是并行执行的。但是开发碰到了要让它们串行执行的需求时,也可以通过 dispatch_set_target_queue
方法来实现。只需要把多个 Serial Dispatch Queue
设置成同一个目标 Serial Dispatch Queue
就可以了。看具体例子:
dispatch_queue_t serialQueue = dispatch_queue_create("io.tao.serial", NULL);
dispatch_queue_t serialQueue1 = dispatch_queue_create("io.tao.app.serial1", NULL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("io.tao.app.serial2", NULL);
dispatch_queue_t serialQueue3 = dispatch_queue_create("io.tao.app.serial3", NULL);
dispatch_set_target_queue(serialQueue1, serialQueue);
dispatch_set_target_queue(serialQueue2, serialQueue);
dispatch_set_target_queue(serialQueue3, serialQueue);
NSLog(@"start...");
dispatch_async(serialQueue1, ^{
sleep(1);
NSLog(@"serialQueue1");
});
dispatch_async(serialQueue2, ^{
NSLog(@"serialQueue2");
});
dispatch_async(serialQueue3, ^{
NSLog(@"serialQueue3");
});
NSLog(@"end...");
// 下面是上面例子的控制台输出。
// serialQueue1, serialQueue2, serialQueue3 成功的按照顺序输出了。
2017-02-23 14:15:21.033 OCGCD[18304:28985032] start...
2017-02-23 14:15:21.033 OCGCD[18304:28985032] end...
2017-02-23 14:15:22.105 OCGCD[18304:28985092] serialQueue1
2017-02-23 14:15:22.105 OCGCD[18304:28985092] serialQueue2
2017-02-23 14:15:22.105 OCGCD[18304:28985092] serialQueue3
dispatch_group
dispatch_group 介绍
dispatch_group
允许把多个 Dispatch Queue 添加到一个 Group 中,等 Group 中所有的 Dispatch Queue 执行完成后,Group 会执行添加的特定 Block 任务
。无论是 Serial Dispatch Queue 还是 Concurrent Dispatch Queue 都是有效的。 这在实际开发中非常有用。看具体例子:
dispatch_queue_t serial = dispatch_queue_create("io.tao.serial", NULL);
dispatch_queue_t concurrent = dispatch_queue_create("io.tao.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, serial, ^{ sleep(3); NSLog(@"serial1");});
dispatch_group_async(group, serial, ^{ sleep(2); NSLog(@"serial2");});
dispatch_group_async(group, concurrent, ^{ sleep(1); NSLog(@"concurrent1");});
dispatch_group_async(group, concurrent, ^{ sleep(2); NSLog(@"concurrent2");});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"Group 中所有的 Dispatch Queue 已执行完");
});
// 下面是上面例子的控制台输出。
2017-02-23 14:30:06.566 OCGCD[19452:29004031] concurrent1
2017-02-23 14:30:07.494 OCGCD[19452:29004033] concurrent2
2017-02-23 14:30:08.494 OCGCD[19452:29004030] serial1
2017-02-23 14:30:10.561 OCGCD[19452:29004030] serial2
2017-02-23 14:30:10.561 OCGCD[19452:29003990] Group 中所有的 Dispatch Queue 已执行完
在添加到 Dispatch Group 中的所有任务全部执行结束后, 就会把 dispatch_group_notify
中的 Block 任务
添加对指定队列中执行。来做一些结束工作。
dispatch_group_wait 的使用
除了通过 dispatch_group_notify
获得 Dispatch Group 执行完的通知,也能通过 dispatch_group_wait
函数来获取结束通知。这个函数会一直等着 Dispatch Group 执行完成才会返回。 它就像是一个断点,把当前线程给断住,直到 Group 的所有任务执行完成。
dispatch_queue_t concurrent = dispatch_queue_create("io.tao.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, concurrent, ^{ sleep(3); NSLog(@"concurrent1");});
dispatch_group_async(group, concurrent, ^{ sleep(2); NSLog(@"concurrent2");});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"Dispatch Group 中所有任务都已执行完毕");
// 上面例子的控制台输出。刚好能证明上面的观点
2017-02-23 14:44:16.883 OCGCD[20511:29021074] concurrent2
2017-02-23 14:44:17.881 OCGCD[20511:29021077] concurrent1
2017-02-23 14:44:17.882 OCGCD[20511:29021036] Dispatch Group 中所有任务都已执行完毕
dispatch_barrier_async
dispatch_barrier_async 介绍
Barrier 如同它的名字一样,它就像一个屏障把 Concurrent Dispatch Queue 里的多个任务给隔离开了。 Concurrent Dispatch Queue里的 Block 任务
是并行执行的,有时候想控制它们执行一部分 Block 任务
后,再执行特定操作,最后执行剩下的其他的 Block 任务
。 dispatch_barrier_async
就可以帮我们做到。看具体例子:
dispatch_queue_t concurrent = dispatch_queue_create("io.tao.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrent, ^{NSLog(@"concurrent 1");});
dispatch_async(concurrent, ^{NSLog(@"concurrent 2");});
dispatch_async(concurrent, ^{NSLog(@"concurrent 3");});
dispatch_barrier_sync(concurrent, ^{ sleep(3); NSLog(@"barrier");});
dispatch_async(concurrent, ^{NSLog(@"concurrent 4");});
dispatch_async(concurrent, ^{NSLog(@"concurrent 5");});
dispatch_async(concurrent, ^{NSLog(@"concurrent 6");});
// 上面例子的控制台输出。注意每条 Log 的时间,concurrent 3 到 barrier 间隔了3秒
2017-02-23 15:17:04.968 OCGCD[22872:29058218] concurrent 1
2017-02-23 15:17:04.968 OCGCD[22872:29058220] concurrent 2
2017-02-23 15:17:04.969 OCGCD[22872:29058217] concurrent 3
2017-02-23 15:17:08.039 OCGCD[22872:29058165] barrier
2017-02-23 15:17:08.040 OCGCD[22872:29058217] concurrent 4
2017-02-23 15:17:08.040 OCGCD[22872:29058220] concurrent 5
2017-02-23 15:17:08.040 OCGCD[22872:29058218] concurrent 6
Dispatch Semaphore
Dispatch Semaphore 是基于计数的信号,在 GCD
中控制并发队列同步的方法之一。 多个队列同时访问修改一个数据时,可能会产生数据不一致的情况,又是程序还会 Carsh。这是就可以通过 Dispatch Semaphore 去解决。
-
dispatch_semaphore_create(1);
会创建一个指定数字的信号量,这里是1。 -
dispatch_semaphore_wait
函数当信号量 >= 1时会把信号量-1,并返回函数 。否则则会一直卡在此处,或者等到超时。 -
dispatch_semaphore_signal
发送一个信号,让信号量+1。
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *marr = [NSMutableArray array];
for (int i=0; i < 9999; ++i) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[marr addObject:[NSNumber numberWithInt:i]];
dispatch_semaphore_signal(semaphore);
});
}
// 上面例子执行完后,marr 里会包含 9999个 NSNumber 对象。因为通过 Dispatch Semaphore 控制始终只有一个线程在操作 marr。所以没有数据竞争的问题
dispatch_suspend / dispatch_resume
这两个函数可以控制 Dispatch Queue 暂停(suspend) 和 恢复(resume)。当 dispatch_suspend(queue)
执行时 queue 被暂停。已经加入到 queue
中的任务会继续执行。后面在再加入的任务会暂停,一直等到 dispatch_resume(queue)
被执行。看具体例子:
dispatch_queue_t queue = dispatch_queue_create("io.tao.app.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{ sleep(2); NSLog(@"queue1"); });
dispatch_async(queue, ^{ NSLog(@"queue2"); });
NSLog(@"queue supend...");
dispatch_suspend(queue);
dispatch_async(queue, ^{ NSLog(@"queue3"); });
sleep(8);
NSLog(@"queue resume...");
dispatch_resume(queue);
// 上面例子的控制台输出。注意每条 Log 的时间。刚好能证明上面的结论
2017-02-23 16:30:19.829 OCGCD[28759:29155110] queue supend...
2017-02-23 16:30:19.829 OCGCD[28759:29155170] queue2
2017-02-23 16:30:21.889 OCGCD[28759:29155168] queue1
2017-02-23 16:30:27.903 OCGCD[28759:29155110] queue resume...
2017-02-23 16:30:27.904 OCGCD[28759:29155167] queue3
GCD 的内存管理
在 iOS6 or Mac OS X 10.8 以前 GCD 对象并没有纳入 ARC
管理范围。通过带 dispatch_xxx_create
创建的值都需开发者负责释放。dispatch_retain
dispatch_release
。iOS6 or Mac OS X 10.8 之后使用的是 ARC 的话就不需要手动管理内存了。