最近的项目遇到了很多多线程的问题,借此机会对GCD进行了一番学习并总结。首先说一下什么是GCD,GCD全称 Grand Central Dispatch,是异步执行任务的技术之一。开发者只需要定义想要执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。说到多线程,我们有必要了解一下GCD的两个核心概念。

一、任务和队列

(1)任务:执行什么操作

(2)队列:用来存放任务

将任务添加到队列中,GCD会自动将队列中的任务取出放到对应的线程中执行,遵循FIFO原则,先进先出,后进后出。

队列分为串行和并行,任务的执行又分为同步和异步。两两组合,串行同步,串行异步,并行同步,并行异步。而异步是多线程的代名词,异步在实际引用中会开启新的线程执行耗时操作。队列只负责任务的调度,而不负责任务的执行,任务是在线程中执行。

二、同步异步,串行并行

(1)同步异步指的是能否开启新的线程。同步不能开启新的线程,异步可以开启。

(2)串行并行指的是任务的执行方式。串行是指多个任务时,各个任务按顺序执行,完成一个后才能进行下一个。并行是指多个任务可以同时执行。(异步是多个任务并行的前提条件!)

三、GCD的API

(1)Main Dispatch Queue

是在主线程中执行任务的Dispatch Queue。因为主线程只有1个,所以Main Dispatch Queue是Serial Dispatch Queue。追加到Main Dispatch Queue中的任务将在主线程的RunLoop中执行。因为是在主线程中执行,所以应该只将用户界面更新等一些必须在主线程中执行的任务追加到Main Dispatch Queue中。

(2)Global Dispatch Queue

是所有应用程序都能使用的Concurrent Dispatch Queue。大多数情况下,可以不必通过dispatch_queue_create函数生成Concurrent Dispatch Queue,而是只要获取Global Dispatch Queue使用即可。Global Dispatch Queue有4个优先级,分别是:High、Default、Low、Background。

(3)dispatch_set_target_queue

dispatch_queue_t mySerailDispatchQueue = dispatch_queue_create("com.example.MySerailDispatchQueue", NULL);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, );
dispatch_set_target_queue(mySerailDispatchQueue, globalDispatchQueueBackground);
// 第一个参数为要设置优先级的queue,第二个参数是参照物,既将第一个queue的优先级和第二个queue的优先级设置一样

一般都是把一个任务放到一个串行的queue中,如果这个任务被拆分了,被放置到多个串行的queue中,但实际还是需要这个任务同步执行,那么就会有问题,因为多个串行queue之间是并行的。

    dispatch_queue_t mySerailDispatchQueue = dispatch_queue_create("com.example.MySerailDispatchQueue", DISPATCH_QUEUE_SERIAL);

    dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL); dispatch_set_target_queue(queue1, mySerailDispatchQueue);
dispatch_set_target_queue(queue2, mySerailDispatchQueue);
dispatch_set_target_queue(queue3, mySerailDispatchQueue); dispatch_async(queue1, ^{
NSLog(@"1 in");
[NSThread sleepForTimeInterval:.f];
NSLog(@"1 out");
}); dispatch_async(queue2, ^{
NSLog(@"2 in");
[NSThread sleepForTimeInterval:.f];
NSLog(@"2 out");
});
dispatch_async(queue3, ^{
NSLog(@"3 in");
[NSThread sleepForTimeInterval:.f];
NSLog(@"3 out");
});

输出结果:

-- ::59.424 GCD-test[:]  in
-- ::02.427 GCD-test[:] out
-- ::02.427 GCD-test[:] in
-- ::04.431 GCD-test[:] out
-- ::04.432 GCD-test[:] in
-- ::05.437 GCD-test[:] out

通过打印的结果说明我们设置了queue1和queue2队列以targetQueue队列为参照对象,那么queue1和queue2中的任务将按照targetQueue的队列处理。

适用场景:
一般都是把一个任务放到一个串行的queue中,如果这个任务被拆分了,被放置到多个串行的queue中,但实际还是需要这个任务同步执行,那么就会有问题,因为多个串行queue之间是并行的。这时候dispatch_set_target_queue将起到作用。

(4)dispatch_after

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull *NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"waited at least three seconds.");
});

该方法的第一个参数time,是指定时间用的dispatch_time_t类型的值,该值使用dispatch_time函数或dispatch_walltime函数生成。

dispatch_time函数能够获取从第一个参数指定的时间开始到第二个参数指定的毫微秒单位时间后的时间。DISPATCH_TIME_NOW表示现在的时间。dispatch_time函数通常用于计算相对时间,而dispatch_walltime函数用于计算绝对时间。

第二个参数dispatch queue,第三个参数要执行的block。需要注意的是,dispatch_after函数并不是在指定时间后执行处理,而只是在指定时间追加处理到dispatch queue。此代码与3秒后用dispatch_async函数追加block到Main Dispatch queue相同。

下面举一个简单的例子,看一下dispatch_after的执行效果:

- (void)dispatch_after
{
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 10ull *NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"hello");
});
_count = ;
_timer = [NSTimer timerWithTimeInterval: target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
} - (void)run{
if (_count == ){
[_timer invalidate];
}
_count ++;
NSLog(@"time: %ld",(long)_count);
}
-- ::27.015 GCD-test[:] time:
-- ::28.015 GCD-test[:] time:
-- ::29.014 GCD-test[:] time:
-- ::30.014 GCD-test[:] time:
-- ::31.015 GCD-test[:] time:
-- ::32.015 GCD-test[:] time:
-- ::33.015 GCD-test[:] time:
-- ::34.014 GCD-test[:] time:
-- ::35.015 GCD-test[:] time:
-- ::36.014 GCD-test[:] time:
-- ::36.015 GCD-test[:] hello
-- ::37.014 GCD-test[:] time:

(5)dispatch group

在追加到dispatch queue中的多个处理全部结束后想执行结束处理,这种情况会经常出现,当然,我们可以使用一个serial dispatch queue,将想要执行的处理全部追加到该dispatch queue中,在最后追加结束处理即可实现,但是在使用concurrent dispatch queue时或者同时使用多个dispatch queue时,就会变得颇为复杂,这个时候我们可以使用dispatch group,无论向什么样的dispatch queue中追加处理,使用dispatch group 都可监视这些处理执行的结束。一旦检测到所有处理执行结束,就可将结束的处理追加到dispatch queue中,这就是使用dispatch queue的原因。

先上代码:

- (void)dispatch_group
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{
NSLog(@"blk0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"done");
});
}
-- ::49.038 GCD-test[:] blk0
-- ::49.038 GCD-test[:] blk2
-- ::49.038 GCD-test[:] blk1
-- ::49.045 GCD-test[:] done

dispatch_group_async函数与dispatch_async函数相同,都追加到Block到指定的dispatch queue中。不同的是指定生成的dispatch group为第一个参数,指定的Block属于指定的dispatch group。

在追加到dispatch group中的处理全部执行结束时,上面的源代码中使用dipatch_group_notify函数会将执行的block追加到dispach queue中,将第一个参数指定为要监视的dispatch group,在追加到该dispatch group的处理全部结束时,将第三个参数的block追加到第二个参数指定的dispatch queue。另外需要强调的是在dispatch_group_notify函数中不管制定了什么样的dispatch queue,属于dispatch group的全部处理在追加指定的block时都已经执行结束。

另外dispatch group中还可以使用dispatch_group_wait函数仅等待全部处理执行结束,还是直接上代码:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"blk0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk2");
});
// dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull *NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == ){
/*
* 属于dispatch group的全部处理执行结束
*/
NSLog(@"done");
}else{
/*
* 属于dispatch group的处理为全部结束
*/
NSLog(@"unfinished");
}
-- ::47.378 GCD-test[:] blk1
-- ::47.378 GCD-test[:] blk2
-- ::47.378 GCD-test[:] blk0
-- ::47.378 GCD-test[:] done

如果dispatch_group_wait返回值不为0,就意味着虽然经过了指定的时间,但属于dispatch group的某一个处理还在进行中,你可以尝试将NSEC_PER_SEC改为NSEC_PER_USEC,这里不做详细介绍了。如果返回值为0,那么全部处理执行结束。如果等待时间为DISPATCH_TIME_FOREVER返回值恒为0,

在主线程的runloop的每次循环中,可检查执行是否结束,从而不耗费多余的等待时间,虽然这样也可以,但一般在这种形势下,推荐使用dispatch_group_notify追加结束处理到main_get_main_queue,因为这样可以简化代码。

(6)diapatch_barrier_async

在访问数据库文件时,如前所述,使用serial dispatch queue可避免数据竞争问题。写入处理确实不可与其他的写入处理以及包含读取处理的其它某些处理并行执行。但是如果读取处理只是与读取处理并行执行,那么多个并行执行就不会发生问题。dispatch_barrier_async函数的作用与barrier的意思相同,在进程管理中起到一个栅栏的作用,它等待所有位于barrier函数之前的操作执行完毕后执行,并且在barrier函数执行之后,barrier函数之后的操作才会得到执行,该函数需要同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用。

1.实现高效率的数据库访问和文件访问。

2.避免数据竞争。

代码示例:

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.ForBarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"dispatch-1");
});
dispatch_async(queue, ^{
NSLog(@"dispatch-2");
});
dispatch_barrier_async(queue, ^{
sleep();
NSLog(@"dispatch-barrier");
});
dispatch_async(queue, ^{
NSLog(@"dispatch-3");
});
dispatch_async(queue, ^{
NSLog(@"dispatch-4");
});
-- ::12.270 GCD-test[:] dispatch-
-- ::12.270 GCD-test[:] dispatch-
-- ::17.275 GCD-test[:] dispatch-barrier
-- ::17.276 GCD-test[:] dispatch-
-- ::17.276 GCD-test[:] dispatch-

(7)dispatch_apply

会等待全部处理执行结束,可用于对nsarray对象的所有元素分别进行处理时,不必一个一个便携for循环部分。

(8)dispatch_sync

同步的将Block追加到指定的Dispatch Queue中。在追加的Block结束之前,Dispatch Queue函数会一直等待。

死锁:dispatch_sync的当前执行队列与提交block执行的目标队列相同时造成死锁。dispatch_sync会堵塞当前线程,等待block执行完毕后返回,但是当前的block加入到了当前的队列等待dispatch_sync的执行完毕,互相依赖,产生死锁。

    dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
NSLog(@"hello");
});

最新文章

  1. 【poj3875】 Lights
  2. Elasticsearch在Windows下的安装
  3. C#常用集合的使用(转载)
  4. TextView实现圆角效果
  5. [reprint]useful linux commands
  6. Struts2原码分析系列之一
  7. 安装percona-toolkit提示的报错
  8. tomcat错误信息解决方案【严重:StandardServer.await: create[8005]
  9. linux命令学习(1)
  10. (Problem 2)Even Fibonacci numbers
  11. Apache Kafka系列(二) 命令行工具(CLI)
  12. DOM中对象的获得
  13. verilog学习笔记(2)_一个小module及其tb
  14. org.springframework.web.context.ContextLoaderListener 解决方案
  15. linux安装openssl
  16. laravel passport加密jwt格式的access_token中的sub(user_id)字段
  17. java中super(),与构造方法链(constructor chaining)
  18. html-edm(邮件营销)编写规则
  19. k64 datasheet学习笔记45---10/100-Mbps Ethernet MAC(ENET)之功能描述
  20. IDC Digital Transition Annual Festival(2018.10.19)

热门文章

  1. 2017《JAVA技术》预备作业
  2. SpringMVC通过实体类返回json格式的字符串,并在前端显示
  3. 自己开发轻量级ORM(二)
  4. oracle_plseq客户端中文乱码
  5. 用css改变console.log的输出样式
  6. Vue2.0源码阅读笔记--生命周期
  7. 【前端】:JavaScript
  8. img图片标签alt和title属性的区别
  9. vue路由表(简单)
  10. 认识ionic2