什么是GCD
GCD是大家现在常用的一种多线程编程技术之一,GCD用非常简洁的记述方法,实现了极为复杂繁琐的多线程编程,是一项划时代的技术。下面是GCD简单的使用实例:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//异步处理的耗时任务
//....
//处理完成后回到主线程进行下一步操作,例如刷新UI。
dispatch_async(dispatch_get_main_queue(), ^{
});
});
什么是线程
1个CPU执行的CPU命令列为一条有序无分叉路径,即为线程。虽然现在CPU技术都已经发展到8核16核等多核CPU,但每个CPU核一次能够执行的CPU命令始终为1,单核CPU执行多线程,其实是在多个线程中快速反复切换,由于切换速度非常之快,因此看上去就好像一个CPU核能够并列的执行多个线程一样。不过现在的多核CPU就是提供了多个CPU核真正并行执行多个线程的技术了。
但是多线程变成是容易发生各种问题的编程技术。比如多个线程更新相同的资源,会导致数据不一致,多个线程互相等待会导致线程死锁,使用太多线程也会消耗大量内存。但多线程为我们带来的高效率的运算,更好的用户体验,就需要我们必须去使用它,在实际使用中规避尽可能这些问题。
GCD的API
Dispatch Queue
Dispatch Queue是执行处理的等待队列。通过dispatch_async函数等API在Block语法中加入要只想的处理,并将其追加到Dispatch Queue中。Dispatch Queue按照追加的顺序执行处理(先进先出)。
Dispatch Queue存在两种Queue,一种是等待执行中处理的Serial Dispatch Queue(按顺序执行的Queue,同时执行一个处理),另一种是不等待现在执行中处理的Concurrent Dispatch Queue(不按固定顺序执行的Queue,可同时执行多个处理)。

dispatch_queue_create
这是GCD提供的生产Dispatch Queue的API
通过dispatch_queue_create函数可以生成Dispatch Queue,代码如下
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.myqueue", NULL);
生成一个Serial Dispatch Queue并追加处理,系统对一个Serial Dispatch Queue就会生成一个线程。Serial Dispatch Queue执行多个处理会比Concurrent Dispatch Queue生成更多线程,为避免资源浪费,尽量控制生成线程的数量。
为了避免多线程同时更新一个资源导致的数据竞争,一般使用Serial Dispatch Queue.
dispatch_queue_create中的第一个参数是Queue的名称,该名称推荐使用逆序全程域名,如com.example.gcd.myqueue,这样命名简单易懂,程序出错时查日志也容易查一些。第二个参数为Concurrent Dispatch Queue或Serial Dispatch Queue,NULL默认为Serial Dispatch Queue。
Dispatch Queue也像其它OC对象一样采用引用计数的方式管理内存。但自己创建的queue需要使用dispatch_release(queue)来释放引用计数。
Main Dispatch Queue/Global Dispatch Queue
第二种方法是获取系统提供的Dispatch Queue。不需要逐一生成,只需要获取系统提供的现成的Queue即可。
Global Dispatch Queue有四个优先级。如下图:

各种Dispatch Queue获取方法如下:
//Main Dispatc Queue的获取方法
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//Global Dispatch Queue的获取方法
//高优先级
dispatch_queue_t highQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//默认优先级
dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//低优先级
dispatch_queue_t lowQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
//后台优先级
dispatch_queue_t backQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
获取到的queue不需要自己去释放,不会发生内存释放的问题。使用更加方便轻松。
dispatch_set_target_queue
该API是用来变更自己生成的queue的优先级的方法。如下代码
/默认优先级
dispatch_queue_t myQueue = dispatch_queue_create("com.example.gcd.myQueue", NULL);
//后台优先级
dispatch_queue_t backQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//将myQueue设置为后台优先级
dispatch_set_target_queue(myQueue, backQueue);
该方法第一个参数只能为自己创建的queue,而不能是系统获取的queue。
将多个Serial Dispatch Queue指定目标为某一个Serial Dispatch Queue,原本并行执行的多个Serial Dispatch Queue,在目标Serial Dispatch Queue上只能同时执行一个处理。
dispatch_after
这是系统提供的延时执行的API.
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"hello queue");
});
其中DISPATCH_TIME_NOW标识当前时间,ull是C语言的数值字面量,是显示表明类型时使用的字符串。NSEC_PER_SEC为单位秒,如果使用NSEC_PER_MSEC则为毫秒。
dispatch_time一般用于计算相对时间,而dispatch_Walltime函数用于计算绝对时间。例如在某日某分某秒的闹钟功能。
Dispatch Group
使用Dispatch Group可以再执行多个Dispatch queue结束后再执行最后的结果,示例代码如下:
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(@"block1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"Game over");
});
执行结果如下:
2018-06-08 15:08:37.831262+0800 ThreadStuty01[1726:413250] block1
2018-06-08 15:08:37.831262+0800 ThreadStuty01[1726:413253] block3
2018-06-08 15:08:37.831262+0800 ThreadStuty01[1726:413251] block2
2018-06-08 15:08:37.834530+0800 ThreadStuty01[1726:411413] Game over
从结果可以看出block的异步执行顺序是不固定的,但Game over是等以上三个线程都执行完后才回到主线程中输出。
另外,在Dispatch Group中也可以使用dispatch_group_wait函数等待全部处理完后再执行的操作。代码如下:
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(@"block1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block3");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"Game Over");
其输出结果为:
2018-06-08 15:11:39.927783+0800 ThreadStuty01[1755:422728] block1
2018-06-08 15:11:39.927783+0800 ThreadStuty01[1755:422730] block2
2018-06-08 15:11:39.927783+0800 ThreadStuty01[1755:422727] block3
2018-06-08 15:11:39.927956+0800 ThreadStuty01[1755:422460] Game Over
其中参数DISPATCH_TIME_FOREVER意味着永久等待,只要group中的操作未执行完,则一直等待。也可以用时间等待,如下
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
及为等待1秒后如果未完成,及执行后续操作。但是在一般情况下还是推荐使用dispatch_group_notify,因为dispatch_group_notify函数可以简化源代码。
dispatch_barrier_async
在访问数据库或文件时,当读取处理与写入处理并行执行,使用Serial Dispatch Queue就会出现问题。
GCD为我们提供了一个新的方法解决该问题,dispatch_barrier_async,该函数同dispatch_queue_create 函数生成的Concurrent Dispatch queue一起使用。代码示例如下:
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.leo", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"b1k0_reading");
});
dispatch_async(queue, ^{
NSLog(@"b1k1_reading");
});
dispatch_async(queue, ^{
NSLog(@"b1k2_reading");
});
dispatch_async(queue, ^{
NSLog(@"b1k3_reading");
});
dispatch_barrier_async(queue, ^{
NSLog(@"b1k_for_writing");
});
dispatch_async(queue, ^{
NSLog(@"b1k4_reading");
});
dispatch_async(queue, ^{
NSLog(@"b1k5_reading");
});
dispatch_async(queue, ^{
NSLog(@"b1k6_reading");
});
dispatch_async(queue, ^{
NSLog(@"b1k7_reading");
});
输出结果如下
2018-06-08 15:29:45.816379+0800 ThreadStuty01[1820:484825] b1k1_reading
2018-06-08 15:29:45.816376+0800 ThreadStuty01[1820:484824] b1k0_reading
2018-06-08 15:29:45.816401+0800 ThreadStuty01[1820:484828] b1k2_reading
2018-06-08 15:29:45.816411+0800 ThreadStuty01[1820:484826] b1k3_reading
2018-06-08 15:29:45.816646+0800 ThreadStuty01[1820:484826] b1k_for_writing
2018-06-08 15:29:45.816759+0800 ThreadStuty01[1820:484826] b1k4_reading
2018-06-08 15:29:45.817057+0800 ThreadStuty01[1820:484824] b1k5_reading
2018-06-08 15:29:45.817071+0800 ThreadStuty01[1820:484825] b1k6_reading
2018-06-08 15:29:45.817102+0800 ThreadStuty01[1820:484828] b1k7_reading
可以看出不管dispatch_barrier_async之前的执行顺序还是之后的,都是等前面读操作执行完后再执行dispatch_barrier_async写操作,dispatch_barrier_async写操作执行完后再执行之后的读操作。这样就避免了b1k4以后的读取不到写入的操作等数据竞争的情况。可以实现高效率的数据库访问和文件访问。
dispatch_sync
async以为着非同步,sync意味着同步,就差了一个字母,很多新手在初学时都傻傻分不清楚。简单示例如下
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.leo", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"hello01");
});
dispatch_sync(queue, ^{
NSLog(@"hello02");
});
dispatch_sync(queue, ^{
NSLog(@"hello03");
});
输出结果为
2018-06-08 15:36:06.495085+0800 ThreadStuty01[1846:505350] hello01
2018-06-08 15:36:06.495231+0800 ThreadStuty01[1846:505350] hello02
2018-06-08 15:36:06.495306+0800 ThreadStuty01[1846:505350] hello03
可以看出结果都是按顺序输出的,后一个必须等前一个执行完才会执行,所以容易引起问题,及死锁。如下代码:
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"hello01");
});
dispatch_sync(queue, ^{
NSLog(@"hello02");
});
dispatch_sync(queue, ^{
NSLog(@"hello03");
});
编译运行,出现崩溃,该源代码在主线程中执行指定的block,并等待其执行结束。而其实在主线程中正在执行这些源代码,所以无法执行追加到主线程中的block。同dispatch_async一样也有dispatch_barrier_sync,效果同dispatch_barrier_async。
dispatch_apply
dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API。该函数指定的次数将指定的block追加到指定的Dispatch Queue中,并等待全部处理执行结束。示例代码如下:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%ld",index);
});
NSLog(@"done");
输出结果如下:
2018-06-08 15:46:41.292013+0800 ThreadStuty01[1908:534650] 0
2018-06-08 15:46:41.292014+0800 ThreadStuty01[1908:534696] 1
2018-06-08 15:46:41.292015+0800 ThreadStuty01[1908:534699] 2
2018-06-08 15:46:41.292022+0800 ThreadStuty01[1908:534698] 3
2018-06-08 15:46:41.292220+0800 ThreadStuty01[1908:534650] 4
2018-06-08 15:46:41.292220+0800 ThreadStuty01[1908:534699] 5
2018-06-08 15:46:41.292221+0800 ThreadStuty01[1908:534696] 6
2018-06-08 15:46:41.292228+0800 ThreadStuty01[1908:534698] 7
2018-06-08 15:46:41.292315+0800 ThreadStuty01[1908:534650] 8
2018-06-08 15:46:41.292320+0800 ThreadStuty01[1908:534699] 9
2018-06-08 15:46:41.292817+0800 ThreadStuty01[1908:534650] done
各个处理的顺序不固定但done是最后执行的。因为dispatch_apply会等待全部处理执行结束。可以用该方法遍历数组,如下:
NSArray *array = @[@"1",@"2",@"3"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(array.count, queue, ^(size_t index) {
NSLog(@"%@",array[index]);
});
dispatch_suspend/dispatch_resume
在处理大量Dispatch Queue时,有时不希望执行某个queue。就可以使用dispatch_suspend挂起该处理,再使用dispatch_resume恢复该处理
及dispatch_suspend(queue);
dispatch_resume(queue);
Dispatch Semaphore
Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号,计数为0时等待,计数为1或者大于1时减去1而不等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *array = [[NSMutableArray alloc]init];
for (int i=0; i<1000; ++i) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:[NSNumber numberWithInt:i]];
});
}
NSLog(@"arrayCount==%ld",array.count);
输出结果为1.dispatch_semaphore_wait将semaphore减一后为0则一直等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *array = [[NSMutableArray alloc]init];
for (int i=0; i<1000; ++i) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:[NSNumber numberWithInt:i]];
dispatch_semaphore_signal(semaphore);
});
}
NSLog(@"arrayCount==%ld",array.count);
输出结果为999, dispatch_semaphore_wait将semaphore减一后dispatch_semaphore_signal将semaphore再加一,则一直执行完所有操作。
dispatch_once
dispatch_once相信大家都不陌生,一般常用在单例中,保证应用程序在执行中只执行一次指定处理的API。下面这种经常出现的用来初始化源代码的也可通过dispatch_once函数处理。
static dispatch_once_t pred;
dispatch_once(&pred, ^{
// 初始化
});
dispatch_once保证了在多线程环境下执行,也可以百分之百的安全
Dispatch I/O
一般在读写大文件时,会将文件分割成合适的大小,并使用Global Dispatch Queue并列读取,会大大的提高读取速度。
dispatch_queue_t queue = dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"读取0~1023字节");
});
dispatch_async(queue, ^{
NSLog(@"读取1024~2047字节");
});
dispatch_async(queue, ^{
NSLog(@"读取2048~4095字节");
});
可以像上面这样,将文件分割为一块块的进行处理,分割读取的数据通过使用Dispatch Data可更为简单的进行结合和分割。
以下为DispatchI/O和Dispatch Data的栗子。:
NSString *desktop = @"/Users/XXX/Desktop";
NSString *path = [desktop stringByAppendingPathComponent:@"main.m"];
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_fd_t fd = open(path.UTF8String, O_RDONLY);
dispatch_io_t io = dispatch_io_create(DISPATCH_IO_RANDOM, fd, queue, ^(int error) {
close(fd);
});
off_t currentSize = 0;
long long fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil].fileSize;
size_t offset = 1024*1024;
dispatch_group_t group = dispatch_group_create();
NSMutableData *totalData = [[NSMutableData alloc] initWithLength:fileSize];
for (; currentSize <= fileSize; currentSize += offset) {
dispatch_group_enter(group);
dispatch_io_read(io, currentSize, offset, queue, ^(bool done, dispatch_data_t _Nullable data, int error) {
if (error == 0) {
size_t len = dispatch_data_get_size(data);
if (len > 0) {
const void *bytes = NULL;
(void)dispatch_data_create_map(data, (const void **)&bytes, &len);
[totalData replaceBytesInRange:NSMakeRange(currentSize, len) withBytes:bytes length:len];
}
}
if (done) {
dispatch_group_leave(group);
}
});
}
dispatch_group_notify(group, queue, ^{
NSString *str = [[NSString alloc] initWithData:totalData encoding:NSUTF8StringEncoding];
NSLog(@"%@", str);
});
网友评论