GCD是Apple开发的一个多核编程的解决方案,他可以用于多核的并行运算,并自动利用更多的CPU内核,同时GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程),而且我们使用GCD时,只需要调用GCD的API来填入需要执行的任务,而不需要编写任何线程管理的代码
一、任务和队列
任务:
执行的操作,任务分为同步执行和异步执行两种。
. 同步(sync):一直在当前线程中执行,而不开辟新的线程
. 异步(async):不同的任务开辟新的线程在各自线程内执行 队列:存放任务的队列,队列是一种特殊的线性表,遵从FIFO(First in first out,先进先出)原则,
队列
分为两种:串行队列和并行队列
. 并行队列(concurrent):多个任务同时执行(每个任务在不同的线程中执行),API为dispatch_async这类函数
. 串行队列(serial):任务按照顺序执行(各个任务可能在同一个线程,也可能在不同的线程),API为dispatch_sync这类函数
二、GCD四种基础操作
1.串行队列 + 同步执行
- (void)dispatchSyncSerial{
//这个函数创建队列,第一个参数为队列名,第二个参数标记创建的队列是串行队列还是并行队列,使用两个宏定义来创建,DISPATCH_QUEUE_SERIAL为串行队列,DISPATCH_QUEUE_CONCURRENT为并行队列
dispatch_queue_t queue = dispatch_queue_create("串行队列名", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
sleep(1);
NSLog(@"在串行队列中执行第一个任务,线程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"在串行队列中执行第二个任务,线程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
sleep(2);
NSLog(@"在串行队列中执行第三个任务,线程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"在串行队列中执行第四个任务,线程:%@",[NSThread currentThread]);
});}
/* 结果
2017-08-22 18:42:55.397 GCD简介与Demo[4463:266445] 在串行队列中执行第一个任务,线程:{number = 1, name = main}
2017-08-22 18:42:55.398 GCD简介与Demo[4463:266445] 在串行队列中执行第二个任务,线程:{number = 1, name = main}
2017-08-22 18:42:57.399 GCD简介与Demo[4463:266445] 在串行队列中执行第三个任务,线程:{number = 1, name = main}
2017-08-22 18:42:57.400 GCD简介与Demo[4463:266445] 在串行队列中执行第四个任务,线程:{number = 1, name = main}
*/
结论:串行队列+同步执行,任务按照添加的顺序,依次执行,并且都在同一个线程中执行
2.串行队列 + 异步执行
- (void)dispatchAsyncSerial{
dispatch_queue_t queue = dispatch_queue_create("串行队列名", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"在串行队列中执行第一个任务,线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"在串行队列中执行第二个任务,线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"在串行队列中执行第三个任务,线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"在串行队列中执行第四个任务,线程:%@",[NSThread currentThread]);
});}
/* 结果 2017-08-22 18:43:45.006 GCD简介与Demo[4481:267419] 在串行队列中执行第一个任务,线程:{number = 3, name = (null)}
2017-08-22 18:43:45.006 GCD简介与Demo[4481:267419] 在串行队列中执行第二个任务,线程:{number = 3, name = (null)}
2017-08-22 18:43:47.011 GCD简介与Demo[4481:267419] 在串行队列中执行第三个任务,线程:{number = 3, name = (null)}
2017-08-22 18:43:47.011 GCD简介与Demo[4481:267419] 在串行队列中执行第四个任务,线程:{number = 3, name = (null)}
结论:串行队列 + 异步执行,虽然各个任务都在在子线程中执行,但仍然是按照添加入队列的顺序,依次执行
*/
3.并行队列 + 同步执行
- (void)dispatchSyncConcurrent{
dispatch_queue_t queue = dispatch_queue_create("并行队列名", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
sleep(1);
NSLog(@"在并行队列中执行第一个任务,线程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"在并行队列中执行第二个任务,线程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
sleep(2);
NSLog(@"在并行队列中执行第三个任务,线程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"在并行队列中执行第四个任务,线程:%@",[NSThread currentThread]);
});}
/* 结果
2017-08-22 18:48:30.946 GCD简介与Demo[4535:272013] 在并行队列中执行第一个任务,线程:{number = 1, name = main}
2017-08-22 18:48:30.947 GCD简介与Demo[4535:272013] 在并行队列中执行第二个任务,线程:{number = 1, name = main}
2017-08-22 18:48:32.948 GCD简介与Demo[4535:272013] 在并行队列中执行第三个任务,线程:{number = 1, name = main}
2017-08-22 18:48:32.949 GCD简介与Demo[4535:272013] 在并行队列中执行第四个任务,线程:{number = 1, name = main}
结论:并行队列 + 同步执行,虽然队里是并行队列,但是由于是同步执行,所有任务都在主线程中执行,他们仍然是按照添加入队列的顺序,依次执行
*/
4.并行队列 + 同步执行
- (void)dispatchAsyncConcurrent{
dispatch_queue_t queue = dispatch_queue_create("并行队列名", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"在并行队列中执行第一个任务,线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"在并行队列中执行第二个任务,线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"在并行队列中执行第三个任务,线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"在并行队列中执行第四个任务,线程:%@",[NSThread currentThread]);
});}
/* 结果
2017-08-22 18:51:02.246 GCD简介与Demo[4574:274917] 在并行队列中执行第二个任务,线程:{number = 3, name = (null)}
2017-08-22 18:51:02.246 GCD简介与Demo[4574:274937] 在并行队列中执行第四个任务,线程:{number = 4, name = (null)}
2017-08-22 18:51:03.249 GCD简介与Demo[4574:274918] 在并行队列中执行第一个任务,线程:{number = 5, name = (null)}
2017-08-22 18:51:04.250 GCD简介与Demo[4574:274920] 在并行队列中执行第三个任务,线程:{number = 6, name = (null)}
结论:并行队列 + 异步执行,各个任务都在子线程中执行,并且同时开始执行
*/
5.GCD中的特殊队列
1.主队列:GCD自带的一个特殊的串行队列,使用dispatch_get_main_queue()获取
2.全局队列:GCD自带的一个特殊的并行队列,使用dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)获取
注意:DISPATCH_QUEUE_PRIORITY_DEFAULT是一个宏,它标记队列的优先级,有以下四种:
. DISPATCH_QUEUE_PRIORITY_HIGH 高优先级
. DISPATCH_QUEUE_PRIORITY_DEFAULT 默认优先级
. DISPATCH_QUEUE_PRIORITY_LOW 低优先级
. DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台优先级
三、线程间的通信以及单次执行方法
1.线程间的通信
一般用于在将网络请求、下载、上传等耗时操作放入子线程执行完毕后,回到主线程更新UI
注意:UI操作必须在主线程进行操作
- (void)gcdSendMessageFromAnotherThread{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"开始执行耗时操作,所处线程:%@",[NSThread currentThread]);
sleep(3);
NSLog(@"耗时操作执行完毕,开始进入主线程");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"进入主线程,开始执行更新UI操作,所处线程:%@",[NSThread currentThread]); });
});}
/* 结果
2017-08-22 19:07:09.099 GCD简介与Demo[4698:290444] 开始执行耗时操作,所处线程:{number = 3, name = (null)}
2017-08-22 19:07:12.103 GCD简介与Demo[4698:290444] 耗时操作执行完毕,开始进入主线程
2017-08-22 19:07:12.103 GCD简介与Demo[4698:290380] 进入主线程,开始执行更新UI操作,所处线程:{number = 1, name = main}
*/
2.单次执行 dispatch_once 一般用于创建单例
- (void)dispatchOnce{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"这里是只执行一次的代码,所处线程:%@",[NSThread currentThread]);
});}
- (void)testDispatchOnce{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"第一次,所处线程:%@",[NSThread currentThread]);
[self dispatchOnce];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"第二次,所处线程:%@",[NSThread currentThread]);
[self dispatchOnce];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"第三次,所处线程:%@",[NSThread currentThread]);
[self dispatchOnce];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"第四次,所处线程:%@",[NSThread currentThread]);
[self dispatchOnce]; }
);}
/* 结果
2017-08-22 19:12:15.813 GCD简介与Demo[4729:295603] 第三次,所处线程:{number = 5, name = (null)}
2017-08-22 19:12:15.813 GCD简介与Demo[4729:295617] 第四次,所处线程:{number = 6, name = (null)}
2017-08-22 19:12:15.813 GCD简介与Demo[4729:295600] 第二次,所处线程:{number = 4, name = (null)}
2017-08-22 19:12:15.813 GCD简介与Demo[4729:295601] 第一次,所处线程:{number = 3, name = (null)}
2017-08-22 19:12:15.814 GCD简介与Demo[4729:295603] 这里是只执行一次的代码,所处线程:{number = 5, name = (null)}
结论:dispatch_once封装的代码只在第一次执行了一次
*/
四、GCD的其它处理函数
1.栅栏方法dispatch_barrier
这个函数相当于一个停顿,在这个函数之前加入队列的任务先执行,执行完毕后执行栅栏函数中的任务,然后再执行栅栏函数之后的任务
注意:dispatch_barrier_async和dispatch_barrier_sync的区别:仅仅是执行的线程不同,dispatch_barrier_sync会同步执行(在主线程中执行),dispatch_barrier_async在子线程中执行
- (void)dispatchBarrier{
dispatch_queue_t queue = dispatch_queue_create("并行队列名", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"第一个任务,线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"第二个任务,线程:%@",[NSThread currentThread]);
});
dispatch_barrier_sync(queue, ^{
sleep(1);
NSLog(@"执行barrier同步任务,线程:%@",[NSThread currentThread]);
sleep(1);
});
dispatch_barrier_async(queue, ^{
sleep(1);
NSLog(@"执行barrier异步任务,线程:%@",[NSThread currentThread]);
sleep(1);
});
dispatch_async(queue, ^{
NSLog(@"第三个任务,线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"第四个任务,线程:%@",[NSThread currentThread]);
});}
/* 结果:
2017-08-22 19:35:16.874 GCD简介与Demo[4949:314086] 第一个任务,线程:{number = 3, name = (null)}
2017-08-22 19:35:16.874 GCD简介与Demo[4949:314083] 第二个任务,线程:{number = 4, name = (null)}
2017-08-22 19:35:17.876 GCD简介与Demo[4949:313797] 执行barrier同步任务,线程:{number = 1, name = main}
2017-08-22 19:35:19.882 GCD简介与Demo[4949:314083] 执行barrier异步任务,线程:{number = 4, name = (null)}
2017-08-22 19:35:20.887 GCD简介与Demo[4949:314083] 第三个任务,线程:{number = 4, name = (null)}
2017-08-22 19:35:20.887 GCD简介与Demo[4949:314084] 第四个任务,线程:{number = 5, name = (null)}
*/
2.GCD延迟执行的方法dispatch_after
- (void)dispatchAfter{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"执行子线程任务,线程:%@",[NSThread currentThread]); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"延时两秒后执行主线程任务,线程:%@",[NSThread currentThread]);
});
}); }
/* 结果
2017-08-22 19:40:10.880 GCD简介与Demo[4982:319700] 执行子线程任务,线程:{number = 3, name = (null)}
2017-08-22 19:40:13.072 GCD简介与Demo[4982:319625] 延时两秒后执行主线程任务,线程:{number = 1, name = main}
*/
3.GCD的快速迭代方法dispatch_apply
dispatch_apply类似for循环,它是一个同步调用,任务将会执行给定次数,如果是并行队里中,那么会并发执行任务,如果是串行队列中,则会顺序执行其中的代码
应用场景:
1.防止创建过多线程导致线程爆炸
2.多个任务并行执行,比如字典数组的快速解析
- (void)dispatchApplyAsync{
NSArray *array = @[@1,@2,@3,@4,@5];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_apply(array.count, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"%@开始执行 %zu times",[NSThread currentThread],index+1);
sleep((unsigned int)(index+1));
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主线程操作");
});
});}
/* 结果
2017-08-22 19:54:38.939 GCD简介与Demo[5088:331292]{number = 5, name = (null)}开始执行 3 times
2017-08-22 19:54:38.939 GCD简介与Demo[5088:331304]{number = 4, name = (null)}开始执行 2 times
2017-08-22 19:54:38.939 GCD简介与Demo[5088:331290]{number = 3, name = (null)}开始执行 1 times
2017-08-22 19:54:38.939 GCD简介与Demo[5088:331289]{number = 6, name = (null)}开始执行 4 times
2017-08-22 19:54:38.940 GCD简介与Demo[5088:331290]{number = 3, name = (null)}开始执行 5 times
2017-08-22 19:54:42.943 GCD简介与Demo[5088:331197] 回到主线程操作
可以看出,dispatch_apply之后的操作将会在dispatch_apply全部执行完毕后才开始执行
*/
- (void)dispatchApplySync{
NSArray *array = @[@1,@2,@3,@4,@5];
dispatch_queue_t queue = dispatch_queue_create("串行队列名", DISPATCH_QUEUE_SERIAL);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_apply(array.count, queue, ^(size_t index) {
NSLog(@"%@开始执行 %zu times",[NSThread currentThread],index+1);
sleep((unsigned int)(index+1));
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主线程操作");
});
});}
/*
结果
2017-08-22 19:58:03.952 GCD简介与Demo[5110:334355]{number = 3, name = (null)}开始执行 1 times
2017-08-22 19:58:04.954 GCD简介与Demo[5110:334355]{number = 3, name = (null)}开始执行 2 times
2017-08-22 19:58:06.958 GCD简介与Demo[5110:334355]{number = 3, name = (null)}开始执行 3 times
2017-08-22 19:58:09.963 GCD简介与Demo[5110:334355]{number = 3, name = (null)}开始执行 4 times
2017-08-22 19:58:13.969 GCD简介与Demo[5110:334355]{number = 3, name = (null)}开始执行 5 times
2017-08-22 19:58:18.972 GCD简介与Demo[5110:334292] 回到主线程操作
可以看出,串行队列中,所有的任务按照顺序依次执行,执行完毕后,再执行后面的代码
*/
4.GCD队列组dispatch_group_t
应用场景:多个任务并发执行,我们需要获取到全部执行完毕后的时间节点(使用dispatch_group_notify获取)
- (void)dispatchGroup{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("并行队列名", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
NSLog(@"执行完第一个任务");
});
dispatch_group_async(group, queue, ^{
sleep(1);
NSLog(@"执行完第二个任务");
});
dispatch_group_async(group, queue, ^{
sleep(2);
NSLog(@"执行完第三个任务");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"任务全部执行完毕");
});
}
/*
2017-08-22 20:06:04.304 GCD简介与Demo[5155:341594] 执行完第一个任务
2017-08-22 20:06:05.306 GCD简介与Demo[5155:341596] 执行完第二个任务
2017-08-22 20:06:06.306 GCD简介与Demo[5155:341593] 执行完第三个任务
2017-08-22 20:06:06.306 GCD简介与Demo[5155:341593] 任务全部执行完毕
*/
五、GCD使用信号量控制并发dispatch_semaphore
1.函数讲解:
.dispatch_semaphore_create
函数原型:dispatch_samaphore_t dispatch_semaphore_create(long value)
传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量。 值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL。
.dispatch_semaphore_signal
函数原型:long dispatch_semaphore_signal(dispatch_semaphore_tdsema)
这个函数会使传入的信号量dsema的值加1
当返回值为0时表示当前并没有线程等待其处理的信号量,其处理的信号量的值加1即可。当返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。
.dispatch_semaphore_wait
函数原型: long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
这个函数会使传入的信号量dsema的值减1
如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t)
不能直接传入整形或float型数,如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。
如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。
dispatch_semaphore_wait的返回值也为long型。当其返回0时表示在timeout之前,该函数所处的线程被成功唤醒。当其返回不为0时,表示timeout发生。
2.timeout参数
.DISPATCH_TIME_NOW 表示当前(立刻超时)
.DISPATCH_TIME_FOREVER 表示遥远的未来(永不超时)
具体可参照:http://www.jianshu.com/p/ed312c734369
- (void)dispatchSemaphore{
//创建信号,设置最大并发数为2
dispatch_semaphore_t sem = dispatch_semaphore_create(2);
for (int i = 0; i < 10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
long a = dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"第%d个任务执行,此时a为:%ld",i+1,a);
sleep(1);
long b = dispatch_semaphore_signal(sem);
NSLog(@"b为:%ld",b);
});
}
}
/*
结果
2017-08-22 21:11:48.928 GCD简介与Demo[716:12343] 第2个任务执行
2017-08-22 21:11:48.928 GCD简介与Demo[716:12330] 第1个任务执行
2017-08-22 21:11:49.931 GCD简介与Demo[716:12329] 第4个任务执行
2017-08-22 21:11:49.931 GCD简介与Demo[716:12332] 第3个任务执行
2017-08-22 21:11:50.933 GCD简介与Demo[716:12345] 第5个任务执行
2017-08-22 21:11:50.933 GCD简介与Demo[716:12346] 第6个任务执行
2017-08-22 21:11:51.937 GCD简介与Demo[716:12347] 第7个任务执行
2017-08-22 21:11:51.937 GCD简介与Demo[716:12348] 第8个任务执行
2017-08-22 21:11:52.940 GCD简介与Demo[716:12350] 第10个任务执行
2017-08-22 21:11:52.940 GCD简介与Demo[716:12349] 第9个任务执行
可以看出,同时执行任务的只有两个,这就控制了最大并发数
*/
六、GCD定时器 dispatch_source_set_timer
NSTimer是我们最为熟悉的定时器,但是NSTimer有很大的缺点,并不准确。而GCD定时器,则是严格按照规定好的规格去做事,它更为准确
NSTimer是在RunLoop的基础上执行的,而RunLoop是在GCD的基础上实现的,所以GCD可以算是更为高级
@interface ViewController ()
//这里不需要*号,因为dispatch_source_t实现中已经加进了指针*号
@property (nonatomic,strong) dispatch_source_t timer;
@property (nonatomic,strong) UIView *animationView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_animationView = [[UIView alloc]initWithFrame:CGRectMake(0, self.view.bounds.size.height/2.-50, 100, 100)];
_animationView.backgroundColor = [UIColor redColor];
[self.view addSubview:_animationView];
[self beginGCDTimer];
}
- (void)beginGCDTimer{
//如果定时器已经开启,则将它关闭,并置空
if (self.timer) {
dispatch_source_cancel(self.timer);
self.timer = nil;
}
//创建队列
dispatch_queue_t queue = dispatch_queue_create("并行队列名", DISPATCH_QUEUE_CONCURRENT);
//创建定时器
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//设置开始时间 后面的参数意思为从现在开始后2秒开始执行定时器回调
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC);
//设置时间间隔 uint64_t为unsigned long long的别名,无符号长长整形
uint64_t interval = 0.01 * NSEC_PER_SEC;
//设置定时器 最后一个参数为偏差,一般设置为0
dispatch_source_set_timer(_timer, start, interval, 0);
//设置回调
dispatch_source_set_event_handler(_timer, ^{
CGFloat x = self.animationView.frame.origin.x;
CGFloat width = self.view.bounds.size.width;
x += 1;
if (x >= width) {
x = 0;
}
CGRect frame = self.animationView.frame;
frame.origin.x = x;
dispatch_async(dispatch_get_main_queue(), ^{
self.animationView.frame = frame;
});
});
//定时器默认是暂停的,需要手动启动
dispatch_resume(_timer);
}
备注:
关于时间的一些宏
#define NSEC_PER_SEC 1000000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
NSEC:纳秒。
USEC:微秒。
SEC:秒
PER:每
1 NSEC_PER_SEC,每秒有多少纳秒。
2 USEC_PER_SEC,每秒有多少毫秒。(注意是指在纳秒的基础上)
3 NSEC_PER_USEC,每毫秒有多少纳秒。
网友评论