GCD两个基本知识:任务和队列;
任务执行方式分为两种:同步(sync)和异步(async)
队列也分为两种:并发队列和串行队列;
各种组合方式:
异步函数+并发队列:开启多条线程,并发执行任务
异步函数+串行队列:开启一条线程,串行执行任务
同步函数+并发队列:不开线程,串行执行任务
同步函数+串行队列:不开线程,串行执行任务
异步函数+主队列:不开线程,在主线程中串行执行任务
同步函数+主队列:不开线程,串行执行任务(发生死锁)
GCD基本函数:
dispatch_barrier_async/dispatch_group_notify/dispatch_semaphore_wait 不会阻塞线程
dispatch_group_wait 会阻塞线程
-
dispatch_barrier_async 控制任务的执行顺序
- queue必须是手动创建的并行队列;
- 所有位于dispatch_barrier_async函数之前的操作执行完毕后dispatch_barrier_async才会执行;
- dispatch_barrier_async函数执行之后,barrier函数之后的操作才会得到执行;
- 该函数需要同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用;
dispatch_queue_t queue = dispatch_queue_create("com.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for(int i = 0; i < 5; i++){
NSLog(@"11111111---%@", [NSThread currentThread]);
}
});
dispatch_barrier_async(queue, ^{
NSLog(@"--dispatch_barrier_async-");
});
dispatch_async(queue, ^{
for(int i = 0; i < 5; i++){
NSLog(@"222222---%@", [NSThread currentThread]);
}
});
执行结果:
testNSThread[1563:634368] 11111111---<NSThread: 0x15457e3b0>{number = 3, name = (null)}
testNSThread[1563:634368] 11111111---<NSThread: 0x15457e3b0>{number = 3, name = (null)}
testNSThread[1563:634368] 11111111---<NSThread: 0x15457e3b0>{number = 3, name = (null)}
testNSThread[1563:634368] 11111111---<NSThread: 0x15457e3b0>{number = 3, name = (null)}
testNSThread[1563:634368] 11111111---<NSThread: 0x15457e3b0>{number = 3, name = (null)}
testNSThread[1563:634366] --dispatch_barrier_async-
testNSThread[1563:634369] 222222---<NSThread: 0x154536fe0>{number = 4, name = (null)}
testNSThread[1563:634369] 222222---<NSThread: 0x154536fe0>{number = 4, name = (null)}
testNSThread[1563:634369] 222222---<NSThread: 0x154536fe0>{number = 4, name = (null)}
testNSThread[1563:634369] 222222---<NSThread: 0x154536fe0>{number = 4, name = (null)}
testNSThread[1563:634369] 222222---<NSThread: 0x154536fe0>{number = 4, name = (null)}
dispatch_barrier_async并不会阻塞线程,同一个并发队列中的操作等待dispatch_barrier_async执行完之后才会执行,其他操作不会等dispatch_barrier_async执行完之后再执行;
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"task1");
});
dispatch_async(queue, ^{
NSLog(@"task2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"barrier_async");
});
dispatch_async(queue, ^{
NSLog(@"task3");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"task4");
});
NSLog(@"---end");
执行结果:
2017-10-12 10:16:29.368495+0800 test[5491:2328009] ---end
2017-10-12 10:16:29.370267+0800 test[5491:2328077] task1
2017-10-12 10:16:29.370539+0800 test[5491:2328733] task2
2017-10-12 10:16:29.370596+0800 test[5491:2328077] task4
2017-10-12 10:16:29.370677+0800 test[5491:2328733] barrier_async
2017-10-12 10:16:29.370795+0800 test[5491:2328733] task3
可以看到task4和输出end 不会等barrier_async执行完再执行;
-
dispatch_after
dispatch_after函数不是延迟N秒再执行block,而是在N秒之后才将block加入到队列中去;
延迟时间有两种计算方式:
1、相对时间:
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC) 表示在当前时间2秒后
DISPATCH_TIME_NOW:当前时间
NSEC_PER_SEC 表示秒数
NSEC_PER_MSEC表示毫秒
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"---%@",[NSThread currentThread]);
});
2、绝对时间:绝对时间需要用 dispatch_walltime 函数创建;
dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);
第一个参数是timespec结构体,
_STRUCT_TIMESPEC
{
__darwin_time_t tv_sec; //秒数的整数部分
long tv_nsec; //秒数的小数部分
};
示例代码:
struct timespec spec = {0, 0};
//获取当前时间做测试
NSDate *date = [NSDate date];
//计算当前时间到1970的秒数
NSTimeInterval timeInterval = [date timeIntervalSince1970];
timeInterval += 5; //在当前时间延迟5秒执行
double second = 0; //秒数的整数部分
double subSecond = 0; //秒数的小数部分
//modf函数的作用是求出整数部分和小数部分
subSecond = modf(timeInterval, &second);
spec.tv_sec = second;
spec.tv_nsec = subSecond * NSEC_PER_SEC;
dispatch_time_t time2= dispatch_walltime(&spec, 0);
dispatch_after(time2, dispatch_get_global_queue(0, 0), ^{
NSLog(@"111111");
});
-
dispatch_once
dispatch_once函数通常用在单例模式上,它可以保证在程序运行期间某段代码只执行一次
-(void)once
{
//整个程序运行过程中只会执行一次
//onceToken用来记录该部分的代码是否被执行过
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"-----");
});
}
-
dispatch_apply
- 把指定的block添加到queue中N次,等到所有block都执行完之后才返回;
- 第一个参数是迭代次数,第二个是所在的队列,第三个是当前索引;
- dispatch_apply可以利用多核的优势;
- dispatch_apply 和 dispatch_apply_f 是同步函数,会阻塞当前线程直到所有循环迭代执行完成;
- 当提交到并发queue时,循环迭代的执行顺序是不确定的 ;
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"%zd", index);
});
输出结果:
2017-04-27 11:13:59.439 newTest[3563:1745064] 1
2017-04-27 11:13:59.439 newTest[3563:1745019] 0
2017-04-27 11:13:59.440 newTest[3563:1745019] 3
2017-04-27 11:13:59.440 newTest[3563:1745064] 2
2017-04-27 11:13:59.441 newTest[3563:1745064] 5
2017-04-27 11:13:59.441 newTest[3563:1745019] 4
2017-04-27 11:13:59.441 newTest[3563:1745064] 6
2017-04-27 11:13:59.441 newTest[3563:1745064] 8
2017-04-27 11:13:59.441 newTest[3563:1745019] 7
2017-04-27 11:13:59.441 newTest[3563:1745064] 9
-
dispatch_group 队列组(同栅栏函数)
下面有两种方式来实现 等待某些操作完成后才开始执行后续操作,
方法1:
dispatch_group_notify:当 dispatch_group中所有的block都执行完后,
dispatch_group_notify会通知队列任务都执行完毕,并执行 dispatch_group_notify 的block;
dispatch_group_notify不会阻塞队列;
dispatch_group_wait : 同步的等待队列中的任务都执行完毕,才会执行后续的任务;会阻塞队列;
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i<10; i++) {
NSLog(@"%zd-download1--%@",i,[NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i<10; i++) {
NSLog(@"%zd-download2--%@",i,[NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
NSLog(@"dispatch_group_notify");
});
方法2:dispatch_group_enter、dispatch_group_wait
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
NSLog(@"group enter");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for(int i = 0; i < 10; i++){
NSLog(@"%zd----%@", i, [NSThread currentThread]);
}
NSLog(@"group leave");
dispatch_group_leave(group);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"another task");
});
需要注意的是,dispatch_group_wait实际上会使当前的线程处于等待的状态,
也就是说如果是在主线程执行dispatch_group_wait,在上面的Block执行完之前,主线程会处于卡死的状态。
可以注意到dispatch_group_wait的第二个参数是指定超时的时间,
如果指定为DISPATCH_TIME_FOREVER(如上面这个例子)则表示会永久等待,直到上面的Block全部执行完,
除此之外,还可以指定为具体的等待时间,根据dispatch_group_wait的返回值来判断是上面block执行完了还是等待超时了。
最后,同之前创建dispatch_queue一样,如果是在OS X 10.8或iOS 6以及之后版本中使用,
Dispatch Group将会由ARC自动管理,如果是在此之前的版本,需要自己手动释放。
-
Dispatch Semaphore
信号量在多线程开发中被广泛使用,当一个线程在进入一段关键代码之前,线程必须获取一个信号量,一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待前面的线程释放信号量。
信号量的具体做法是:当信号计数大于0时,每条进来的线程使计数减1,直到变为0,变为0后其他的线程将进不来,处于等待状态;执行完任务的线程释放信号,使计数加1,如此循环下去。
下面这个例子中使用了10条线程,但是同时只执行一条,其他的线程处于等待状态:
//dispatch_semaphore_wait :减少信号量计数.如果结果小于0, 等待信号唤醒.
//dispatch_semaphore_signal : 增加信号量计数,如果之前的值小于0 唤醒 dispatch_semaphore_wait
//第一步:初始信号量= 0
semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for(NSInteger i = 0; i < 10; i++){
NSLog(@"--------------%zd", i);
}
//第三步:任务执行完 判断信号量小于0 唤醒dispatch_semaphore_wait
dispatch_semaphore_signal(semaphore);
});
//第二步:信号量减1 变成 -1,小于0 等待唤醒
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"dddddddddddddddddd");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
});
取得信号量的线程在2秒后释放了信息量,相当于是每2秒执行一次。
通过上面的例子可以看到,在GCD中,用dispatch_semaphore_create函数能初始化一个信号量,同时需要指定信号量的初始值;
使用dispatch_semaphore_wait函数分配信号量并使计数减1,为0时处于等待状态;
使用dispatch_semaphore_signal函数释放信号量,并使计数加1。
另外dispatch_semaphore_wait同样也支持超时,只需要给其第二个参数指定超时的时候即可,
同Dispatch Group的dispatch_group_wait函数类似,可以通过返回值来判断。
这个函数也需要注意,如果是在OS X 10.8或iOS 6以及之后版本中使用,Dispatch Semaphore将会由ARC自动管理,
如果是在此之前的版本,需要自己手动释放。