iOS中GCD的实现与总结
GCD全称"Grand Central Dispatch", 中央调度的意思, 是iOS中标准异步处理的技术.
为什么会产生GCD呢, 这个跟其他语言类比下, 在Java中多使用线程, Erlang中使用协程, 其中协程比线程更加轻量更加优雅, 而iOS中的GCD就可以类比为协程这样让代码更加优雅的东西.
本篇内容是《Objective-C高级编程 iOS与OS X多线程和内存管理》的最后一部分, 也代表对这本书整理完毕了.
目录:
- GCD的基础使用
- GCD的分类
- GCD相关的函数及其使用
- dispatch_queue_create
- dispatch_get_main_queue
- dispatch_get_global_queue
- dispatch_set_target_queue
- dispatch_after
- dispatch_group
- dispatch_barrier
- dispatch_sync
- dispatch_apply
- dispatch_suspend/dispatch_resume
- dispatch_semaphore
- dispatch_once
- dispatch I/O
- GCD的实现
- 总结
GCD的基础使用
GCD的代码比较优雅, 应用简单, 也很好理解, 下面是一段示例代码:
dispatch_async(queue, ^{
// 后台长时间的处理
dispatch_async(dispatch_get_main_queue(), ^{
// 主线程(UI线程)处理
});
});
其中queue是"dispatch_queue_t"类型的变量, block是执行的代码块.
这段代码也可以使用NSObject的两个静态方法实现,
[NSObject performSelectorOnMainThread:<#(SEL)aSelector#> withObject:<#(id)arg#> waitUntilDone:<#(BOOL)wait#>];
[NSObject performSelectorInBackground:<#(SEL)aSelector#> withObject:<#(id)arg#>];
但是代码就会变得有点散杂乱了.
GCD的分类
GCD的Queue总共分为两大类:
- Serial Dispatch Queue
- Concurrent Dispatch Queue
"Serial Dispatch Queue"是一个串行的队列, 添加到这种queue的block会依次执行. 但是也可以创建多个不同标识的这种queue, 它们内部的block依然是依次执行的, 但是这些queue之间是同时执行的. 需要注意的是, 一个serial类型的queue会对应一个线程, 创建大量的线程会损耗系统的性能.
"Concurrent Dispatch Queue"是"共享"多个串行的队列的结构, 每个队列跟serial类型的queue并无差别, 而队列的个数一般与操作系统的CPU个数相关.
上面"共享"的意思是不管你创建多少了concurrent类型的queue, 它们能使用的队列个数都是确定的一个值.
创建方式也很简单:
dispatch_queue_t serialQueue = dispatch_queue_create("com.jegarn.serial_dispatch_queue", nil);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.jegarn.concurrent_dispatch_queue", DISPATCH_QUEUE_CONCURRENT);
第一个参数是string类型的唯一标识, 第二个是Queue的类型标识.
需要注意的是"dispatch_queue_t"是C语言的结构体, 所以其并不在ARC的管理下, 所以在不使用的时候, 需要调用release方法释放.
dispatch_release(serialQueue);
当然也可以增加其引用计数, 这与MRC管理内存的方式一致.
dispatch_retain(concurrentQueue);
上面我们有提到"dispatch_get_main_queue(void)"函数, 其实这是系统为我们提供获取主UI线程的queue的方法, 因为主UI线程的执行都是串行的, 那么获取也就是一个serial类型的queue.
系统还为我们提供一个函数"dispatch_get_global_queue(,)", 这是为我们提供了一个全局的concurrent类型的queue.
用这两个函数的好处就是我们不需要做额外的内存管理了, 因为都是系统帮我们创建的, 其管理交给系统就好了.
"dispatch_get_global_queue(,)"函数有两个参数, 第一个参数是设置放到这个queue里面的block的执行优先级, 第二个参数是一个flag, 传入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
根据我们上面对concurrent类型的queue的解释, 这个优先级并不一定就控制了block被执行的先后顺序, 它应该是一个建议的参数.
GCD相关的函数及其使用
dispatch_queue_create
这个函数我们已经在上面详细的讲解了, 其函数原型为:
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
dispatch_get_main_queue
这个函数我们已经在上面详细的讲解了, 其函数原型为:
dispatch_queue_t dispatch_get_main_queue(void);
dispatch_get_global_queue
这个函数我们已经在上面详细的讲解了, 其函数原型为:
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
dispatch_set_target_queue
其函数原型为:
void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
其含义类似于把一个queue当成block放到另一个queue里面去.
另一个就是通过"dispatch_queue_create()"获取的queue默认优先级是"DISPATCH_QUEUE_PRIORITY_DEFAULT", 所以我们还可以通过这个函数改变其优先级.
dispatch_after
dispatch_after可以设置一个block在指定的时间执行
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
比如下面代码我们设置一个block在3秒后执行
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^ { printf("hello\n"); });
dispatch_group
dispatch_group可以设置在多个block都执行完毕后回调一个block, 其用法的示例代码如下:
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, ^{ printf("1"); });
dispatch_group_async(group, queue, ^{ printf("2"); });
dispatch_group_async(group, queue, ^{ printf("3"); });
dispatch_group_notify(group, dispatch_get_main_queue(), ^{ printf("all done!"); });
dispatch_release(group);
我们也可以使用函数阻塞的等待添加的block执行, 其在等待了执行的时间之后, 如果返回0则代表所有block都执行完毕, 否则即有block仍在执行中.
其函数原型为:
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
dispatch_barrier
这个函数的原型为:
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
这个函数一般作用于concurrent类型的queue上, 用于将调用这个函数前后的block分为两组, 前面的block组并发执行完毕后, 才会单独执行"dispatch_barrier_async"设置的block, 然后再并发执行后面的block组.
这个也能通过group实现, 但是barrier提供了更简单的实现.
dispatch_sync
其函数原型为:
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
"dispatch_sync"从名字上也能看出是同步的处理, 意味该函数会一直阻塞到其设置的block执行完毕.
dispatch_apply
dispatch_apply能够实现把设置的block执行指定的次数.
dispatch_apply(10, queue, ^(size_t index){
printf("i am %d\n", index);
});
dispatch_suspend/dispatch_resume
dispatch_suspend()函数能够暂停queue, 将其挂起, 那么其中还未开始执行的block就会处于等待的状态, 然后调用dispatch_resume()函数能够再次恢复执行.
dispatch_semaphore
dispatch_semaphore几乎跟操作系统的PV操作一致, 通过信号量保证资源的互斥访问.
NSMutableArray * array = [[NSMutableArray alloc] initWithCapacity:10000];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < 10000; ++i) {
dispatch_async(queue, ^{
// 一直等待semaphore的value大于等于1, 并且将value减1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:@(i)];
// 将semaphore的value加1
dispatch_semaphore_signal(semaphore);
});
}
dispatch_release(semaphore);
dispatch_once
dispatch_once能够保证其设置的block被执行一次, 多用来实现单例模式.
static id instance;
+ (instancetype)shareInstance {
static dispatch_once_t once;
dispatch_once(&once, ^{
instance = [[XxxClass alloc] init];
});
return instance;
}
dispatch I/O
当我们读取大文件时, 其实可以通过concurrent的queue并发读取, 但是已经iOS为我们提供这方面的支持.
下面是一段示例代码:
pipe_q = dispatch_queue_create("PipeQ", NULL);
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
close(fd);
});
*out_fd = fdpair[1];
dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
if (err == 0)
{
size_t len = dispatch_data_get_size(pipedata);
if (len > 0)
{
const char *bytes = NULL;
char *encoded;
dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
encoded = asl_core_encode_buffer(bytes, len);
asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded);
free(encoded);
_asl_send_message(NULL, merged_msg, -1, NULL);
asl_msg_release(merged_msg);
dispatch_release(md);
}
}
if (done)
{
dispatch_semaphore_signal(sem);
dispatch_release(pipe_channel);
dispatch_release(pipe_q);
}
});
GCD的实现
GCD中最多出现的字就是queue, 而queue即是一个先进先出的队列, 队列的实现方式就很多了.
然后并发的执行难免会使用多线程, GCD是在内核级别的多线程的运用, 不同于pthreads/NSThread这种直接对应操作系统线程的方式.
多线程中的资源操作必然会使用到锁, 在上面的函数中我们也看到GCD有使用semaphore信号量.
最后在服务器编程中, 实现高效的运行一般会运用到事件驱动, 在linux中一般为epoll, 而iOS中则为kqueue.
GCD即是通过这些基础的东西实现的, 而且是优雅的, 易用的.
总结
花了两天时间, 把《Objective-C高级编程 iOS与OS X多线程和内存管理》一书全部读完了, 全书分为3章:内存管理,Blocks,GCD. 我也对应的写了3篇总结, 几乎涉及到应用的部分我都有记录下来, 而具体实现的部分实在太过冗长, 也就基本一笔带过了.
读完这本数, 对iOS一些基础的东西有了更清晰的认识, 基本算是入门了.