GCD语法笔记

  • 并行+异步:就是真正的并发,新开有有多个线程处理任务,任务并发执行(不按顺序执行)
  • 串行+异步:新开一个线程,任务一个接一个执行,上一个任务处理完毕,下一个任务才可以被执行
  • 并行+同步:不新开线程,任务一个接一个执行
  • 串行+同步:不新开线程,任务一个接一个执行

基本常用用法

1.最常用用法

1
2
3
4
5
6
7
8
9
10
11
12
//创建异步线程并运行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int index = 0; index < 10; index++) {
sleep(1);
//异步切换到主线程,可以在里面刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"_______%d",index);

});

}
});

dispatch_async //异步线程函数

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)//获取一个全局队列,并设置队列的优先级,一般设置DEFAULT
优先级设置的种类:

#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//后台运行

0保留字段备用,一般设置为0

2.dispatch_queue_t挂起和继续

我们可以暂停一个 queue 以阻止它执行 block 对象,使用 dispatch_suspend 函数挂起一个 dispatch queue;使用 dispatch_resume 函数继续 dispatch queue。调 用 dispatch_suspend 会增加 queue 的挂起计数,调用 dispatch_resume 则减少queue 的挂起计数。当挂起计数大于 0 时,queue 就保持挂起状态。因此你必须对应地调用 suspend 和 resume 函数。

挂起和继续是异步的,而且只在执行 block 之间生效。挂起一个 queue 不会导致正在执行的 block 停止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//创建一个`dispatch_queue_t`
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//异步运行这个`dispatch_queue_t`
dispatch_async(aQueue, ^{
for (int index = 0; index < 10; index++) {
sleep(1);
NSLog(@"_______%d",index);
}
});
//挂起dispatch queue
dispatch_suspend(aQueue);
//继续dispatch queue
dispatch_resume(aQueue);
//应当成对出现 (线程的挂起和继续是异步操作)

3.Dispatch Queue 和线程安全性

  • 使用 Dispatch Queue 实现应用并发时,也需要注意线程安全性:

  • Dispatch queue 本身是线程安全的。换句话说,你可以在应用的任意线程中提交任务到 dispatch queue,不需要使用锁或其它同步机制。

  • 不要在执行任务代码中调用 dispatch_sync 函数调度相同的 queue,这样做会死锁这个 queue。如果你需要 dispatch 到当前 queue,需要使用 dispatch_async 函数异步调度。

  • 避免在提交到 dispatch queue 的任务中获得锁,虽然在任务中使用锁是安全的,但在请求锁时,如果锁不可用,可能会完全阻塞串行 queue。类似的,并发 queue 等待锁也可能阻止其它任务的执行。如果代码需要同步,就使用串行 dispatch queue。

  • 虽然可以获得运行任务的底层线程的信息,最好不要这样做。

4.Dispatch Group

用于监控一组 Block 对象完成(你可以同步或异步地监控 block)。Group 提供了一个非常有用的同步机制,你的代码可以等待其它任务的完成。

使用 Dispatch Group 等待 queue 中的一组任务

Dispatch group 用来阻塞一个线程,直到一个或多个任务完成执行。有时候 你必须等待任务完成的结果,然后才能继续后面的处理。dispatch group 也可以替代线程 join。

基本的流程是设置一个组,dispatch 任务到 queue,然后等待结果。你需要使用 dispatch_group_async 函数,会关联任务到相关的组和 queue。使用 dispatch_group_wait 等待一组任务完成。

1
2
3
4
5
6
7
8
9
10
11
12
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//创建dispatch_group
dispatch_group_t group = dispatch_group_create();
// 把 queue 加入到 group
dispatch_group_async(group, queue, ^{
// 一些异步操作任务
});

// code 你可以在这里写代码做一些不必等待 group 内任务的操作

// 当你在 group 的任务没有完成的情况下不能做更多的事时,阻塞当前线程等待 group 完工
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

5. dispatch_group_notify

dispatch_group 执行完一组异步操作后可以通过 dispatch_group_notify来通知主线程,反馈信息给用户。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  //真实的异步并发多个异步任务
//创建dispatch_group
dispatch_group_t group = dispatch_group_create();
// 自定义并发队列
dispatch_queue_t queue = dispatch_queue_create("com.customerName.www", DISPATCH_QUEUE_CONCURRENT);
//自定义异步并发任务并添加到dispatch_group和queue里
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 1000; i++) {
if (i == 999) {
NSLog(@"11111111");
}
}

});
//自定义异步并发任务并添加到dispatch_group和queue里
dispatch_group_async(group, queue, ^{
NSLog(@"22222222");
});
//自定义异步并发任务并添加到dispatch_group和queue里
dispatch_group_async(group, queue, ^{
NSLog(@"33333333");
});
//自定义异步并发任务并添加到dispatch_group和queue里
dispatch_group_notify(group, queue, ^{
NSLog(@"done");
});

因为向Concurrent Dispatch Queue 追加处理,多个线程并行执行,所以追加处理的执行顺序不定。执行顺序会发生变化,但是此执行结果的done一定是最后输出的。

无论向什么样的Dispatch Queue中追加处理,使用Dispatch Group都可以监视这些处理执行的结果。一旦检测到所有处理执行结束,就可以将结束的处理追加到Dispatch Queue中,这就是使用Dispatch Group的原因。

下面试一个使用Dispatch Group异步下载两张图片,然后合并成一张图片的medo(注意,我们总是应该在主线程中更新UI):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UIImage *imageOne;
@property (nonatomic, strong) UIImage *imageTwo;
@property (nonatomic, weak) UILabel *textLabel;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

[self operation1];
}

- (void)operation1
{
UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(200, 450, 0, 0)];
textLabel.text = @"正在下载图片";
[textLabel sizeToFit];
[self.view addSubview:textLabel];
self.textLabel = textLabel;
[self group];
NSLog(@"在下载图片的时候,主线程貌似还可以干点什么");
}


- (void)group
{
UIImageView *imageView = [[UIImageView alloc] init];
[self.view addSubview:imageView];

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("cn.gcd-group.www", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_async(group, queue, ^{
NSLog(@"正在下载第一张图片");
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://images2015.cnblogs.com/blog/471463/201509/471463-20150912213125372-589808688.png"]];
NSLog(@"第一张图片下载完毕");
self.imageOne = [UIImage imageWithData:data];
});

dispatch_group_async(group, queue, ^{
NSLog(@"正在下载第二张图片");
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://images2015.cnblogs.com/blog/471463/201509/471463-20150912212457684-585830854.png"]];
NSLog(@"第二张图片下载完毕");
self.imageTwo = [UIImage imageWithData:data];
});

dispatch_group_notify(group, queue, ^{
UIGraphicsBeginImageContext(CGSizeMake(300, 400));

[self.imageOne drawInRect:CGRectMake(0, 0, 150, 400)];
[self.imageTwo drawInRect:CGRectMake(150, 0, 150, 400)];

UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

dispatch_async(dispatch_get_main_queue(), ^{
UIImageView *imageView = [[UIImageView alloc] initWithImage:newImage];
[self.view addSubview:imageView];
self.textLabel.text = @"图片合并完毕";
});
});
}
@end

注意:dispatch_group_notify对串行队列无意义
自定义串行队列

1
dispatch_queue_t myCustomQueue = dispatch_queue_create("test.customQueueName.MyCustomQueue", DISPATCH_QUEUE_SERIAL);

因为串行队列里面任务本来就是一个执行完毕再接着执行下一个,所以我们只需在最后一个异步任务处理之前所执行的任务结果就可以了,据说串行队列没有并发队列效率高。

6. dispatch_barrier

资源锁

在队列中,barrier块必须单独执行,不能与其他block并行。这只对并发队列有意义,并发队列如果发现接下来要执行的block是个barrier block,那么就一直要等到当前所有并发的block都执行完毕,才会单独执行这个barrier block代码块,等到这个barrier block执行完毕,再继续正常处理其他并发block。在上面的代码中,setter方法中使用了barrier block以后,对象的读取操作依然是可以并发执行的,但是写入操作就必须单独执行了。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#import <Foundation/Foundation.h>

@interface ZYPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end


#import "ZYPerson.h"

@interface ZYPerson ()
@end

static NSString *_name;
static dispatch_queue_t _concurrentQueue;
@implementation ZYPerson
- (instancetype)init
{
if (self = [super init]) {
_concurrentQueue = dispatch_queue_create("com.person.syncQueue", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
- (void)setName:(NSString *)name
{
dispatch_barrier_async(_concurrentQueue, ^{
_name = [name copy];
});
}
- (NSString *)name
{
__block NSString *tempName;
dispatch_sync(_concurrentQueue, ^{
tempName = _name;
});
return tempName;
}
@end

7.使用 Dispatch Semaphore 控制有限资源的使用

1
2
3
4
5
6
7
8
9
10
// 创建信号,指定初始池大小
dispatch_semaphore_t sema = dispatch_semaphore_create(getdtablesize() / 2);

// 等待一个可用的文件描述符
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
// code 这里写代码获取文件并对其进行操作

// 完成后释放文件描述符
// code 这里写代码关闭文件
dispatch_semaphore_signal(sema);

8.Dispatch Source

Dispatch Source 在特定类型的系统事件发生时,会产生通知。你可以使用 Dispatch Source 来监控各种事件,如:进程通知、信号、描述符事件、等等。当事件发生时,Dispatch Source 异步地提交你的任务到指定的 Dispatch Queue 来进行处理。

看了博客但是还没有想得到具体使用的业务场景,待补充