标签:
GCD对于iOS开发者来说肯定不陌生,他和NSThread,NSOperation一起作为iOS开发中主要的三种多线程实现方法,而GCD是最最底层的,所以对于作为一个iOSer,GCD是必须掌握的。
我通过对于以下两篇文章的阅读,基本上掌握了GCD的基本使用方法。所以首先感谢两位作者。
对于新手来说,最常见同时最容易搞混的的莫过于GCD中的一些基本概念了。
并行与并发(Parallelism && Concurrency)
串行与并发(Serial && Concurrent)
队列:就像前面所说,当你告诉电脑你需要执行哪些事情的时候,电脑就会把你告诉他的一件件事情放到自己的队伍中,很明显,先告诉他的事情放在前面,后告诉他的事情放在后面,即先进先处理(FIFO)原则处理所有事物,就像排队一样。
既然学了,那肯定要使用它。那么我们先模拟一个环境:
这个程序里面只有主队列,然后你想从网上下载一个图片,放到你自己的手机里面,然后用户就可以对手机里面的这张图片进行各种各样的处理。
但是问题来了:我们都知道下载需要时间,虽然现在已经是4G时代,下载速度很快,但是如果这个图片很大,那么他就需要很长的时间来进行等待。而在这段时间内,用户什么事情都不能干,只能默默的干等着。
这很明显是一个很差的用户体验
那么既然我们已经知道问题所在,就需要想办法解决它:
那么这个时候就需要异步队列的存在了
就像前面的基本概念中所提到的,执行分为同步执行和异步执行,任务队列分为串行队列和并发队列。所以他们彼此一一结合,总共有4种情况存在,主要的彼此结合的状态图如下所示:
代码如下:
func serialDispatchQueueWithSync() {
let queue = dispatch_queue_create("serialDispatchQueue", DISPATCH_QUEUE_SERIAL)
print("0")
dispatch_sync(queue) {
print("1")
}
dispatch_sync(queue) {
print("2")
}
dispatch_sync(queue) {
print("3")
}
dispatch_sync(queue) {
print("4")
}
dispatch_sync(queue) {
print("5")
}
print("6")
}
运行结果如下:
0
1
2
3
4
5
6
代码如下:
func serialDispatchQueueWithASync() {
let queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL)
print("0")
dispatch_async(queue) {
print("1")
}
dispatch_async(queue) {
print("2")
}
dispatch_async(queue) {
print("3")
}
dispatch_async(queue) {
print("4")
}
dispatch_async(queue) {
print("5")
}
dispatch_async(queue) {
print("6")
}
print("7")
}
运行结果如下:
0
7
1
2
3
4
5
6
解释:因为queue是异步执行的,即调用dispatch_async函数,所以输出1 2 3 4 5 6的时候是在另一个线程中的,所以和主线程中的输出1 7 无关。
代码如下:
func concurrentDispatchsQueueWithSync() {
let queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT)
print("0")
dispatch_sync(queue) {
print("1")
print("12")
print("13")
}
dispatch_sync(queue) {
print("2")
print("22")
print("23")
}
dispatch_sync(queue) {
print("3")
print("32")
print("33")
}
dispatch_sync(queue) {
print("4")
print("42")
print("43")
}
print("5")
}
运行结果如下:
0
1
12
13
2
22
23
3
32
33
4
42
43
5
代码如下:
func concurrentDispatchsQueueWithASync() {
let queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT)
print("0")
dispatch_async(queue) {
print("1")
print("12")
print("13")
}
dispatch_async(queue) {
print("2")
print("22")
print("23")
}
dispatch_async(queue) {
print("3")
print("32")
print("33")
}
dispatch_async(queue) {
print("4")
print("42")
print("43")
}
print("5")
}
运行结果如下:
0
7
1
2
12
3
22
13
4
32
23
5
42
33
52
43
53
dispatch_suspnd()
dispatch_resume()
由于有的时候(比如说在创建添加数组中的对象的时候),因为不同线程操纵的是同一个对象,所以很容易发生报错,这个时候需要通过信号量来对对应的内容进行控制,当信号量为0的时候,进入等待状态,不能执行下面的内容;当信号量为1的时候,可以执行,同时信号量减一,等到执行完毕,信号量加一。
- 等待执行dispatch_semaphore_wait()
- 信号量加一 dispatch_semahore_signal
有的时候,有的东西的创建只能被创建一次(即单例),这个时候就需要用到dispatch_once()
代码如下:
var token: dispatch_once_t = 0
func test() {
dispatch_once(&token) {
println("This is printed only on the first call to test()")
}
println("This is printed for each call to test()")
}
由于日常编码的过程中经常会遇到,当你在调用这个这个变量的时候。另一个线程也在调用这个变量(只在并发过程中),如果两个线程都是在读取数据,那并没有什么问题,但是如果其中一个在写入,或者两个都在写入,那么就会出现很大问题,所以苹果官方在GCD中也给我们准备了一个方法,让我们解决这个问题,那就是dispatch_barrier_async
,这个方法让对的内容在并发过程中加入,从而方便组织内容的修改,从而使得对应的内容只能在当前线程中被进行修改。
let queue = dispatch_get_main_queue()
dispatch_async(queue) {
dispatch_sync(queue, {
print("1")
})
}
产生原理:因为dispatch_sync()
会等到本身结束之后才会在主线程继续执行接下去的代码,但是dispatch_sync()
这个方法调用的就是主线程,所以午饭等到主线程结束,所以就无法返回,就会卡在这里。
解释:由于是并发队列,所以他会创建多个线程,从而保证每个线程的任务都能够尽快完成,所以顺序有一定的出入。
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"First Log");
});
NSLog(@"Second Log");
}
dispatch_sync
dispatch_sync
代码,将block添加到全局队列中,主队列挂起。dispatch_sync
的block中的内容。- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"First Log");
});
NSLog(@"Second Log");
}
![dispatch_sync](https://camo.githubusercontent.com/2c7cbaf76001a56622e14cf48a8d914d4b5c9df4/687474703a2f2f63646e312e72617977656e6465726c6963682e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30312f64697370617463685f6173796e635f696e5f616374696f6e2e676966)
UIViewController
的任务,其中包含了viewDidLoad
。viewDidLoad
在主线程执行。viewDidLoad
内,正要到达dispatch_async
。dispatch_async Block
被添加到一个全局队列中,将在稍后执行。viewDidLoad
在添加dispatch_async
到全局队列后继续进行,主线程把注意力转向剩下的任务。同时,全局队列并发地处理它未完成地任务。记住Block
在全局队列中将按照(FIFO)顺序出列,但可以并发执行。dispatch_async
的代码块开始执行。dispatch_async Block
完成,两个NSLog
语句将它们的输出放在控制台上。既然能够处理一个个的任务,那么我们就继续模拟一个环境,当我们需要在网上下载内容的时候(这些内容需要彼此联系在一起才能正常使用),这个时候,上面的单队列就不够了(或者说如果使用单队列产生的效果不是时间太长,就是文件的完整性不够好)
这个时候我们就需要引入多队列,当多个内容都处理完成之后,让系统告诉我们,我们已经完成了以上的下载。可以继续做下一步事情了。
在GCD中,我们可以通过dispatch_group_enter
来通知当前任务的开始,而与之相对应的,我们必须在任务完成后,手动通知调度组任务结束(dispatch_group_leave
)这样才能让调度组知道我们这个任务已经结束。
代码如下(由于demo中有一个Photo类,所以此处贴上OC代码):
dispatch_group_enter(downloadGroup); // 3
Photo *photo = [[Photo alloc] initwithURL:url withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
dispatch_group_leave(downloadGroup); // 4
}];
[[PhotoManager sharedManager] addPhoto:photo];
当我们所有的任务都手动通知后,那么就需要条用提醒来告诉他(我已经完成了所有内容,接下去需要试试哪里能够执行了),而提醒方式在GCD中有两种:
dispatch_group_wait() + dispatch_async()
dispatch_group_notify
有的时候需要调用for循环来反复执行,但是当需要执行的代码量偏大的时候,for的效率比较低,这个时候需要用dispatch_apply()
来执行,这样节省效率,不过当需要执行的代码量比较小的时候,dispatch_apply()
的效率就比较差了。
这个方法的效果和
dispatch_sync
一样,所以要注意死锁(后面会提到)
标签:
原文地址:http://blog.csdn.net/zkh90644/article/details/51934062