GCD中涉及的基本名词:
线程:程序执行任务的最小调度单位
任务:需要在主线程或者子线程中执行的代码,在GCD中显示为Block中
队列:一种特殊的线性表,采取FIFO(先进先出),可以看做是用来存储任务的数组
异步:开辟新线程,同时执行多个任务
同步:在单一线程中执行,只能按顺序从前往后执行
并行队列:队列中的任务同时进行,可以开启多个线程,并发功能只有在异步情况有效
串行队列:队列中的任务逐个进行,只有单一线程
GCD的创建:
概述的讲,就是创建一个队列,然后将任务添加到队列中去
(1)创建队列
// ?????????串行队列
dispatch_queue_t queue = dispatch_queue_create("com.app", DISPATCH_QUEUE_SERIAL);
// ?????????并发队列
dispatch_queue_t queue = dispatch_queue_create("com.app", DISPATCH_QUEUE_CONCURRENT);
第一个参数是队列的标志符,可以为空,第二个参数表示是串行队列还是并发队列
GCD中有简便的方法获取队列,一个是获取主队列(串行队列),还有一个是获取全局并发队列(并发队列)
//主队列
dispatch_queue_t queue = dispatch_get_main_queue();
//全局并发队列,第一个参数表示优先级,第二个参数是保留参数,暂时无用
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DE FAULT, 0);
(2)创建任务
dispatch_sync(queue, ^{
// 同步
});
???????dispatch_async(queue, ^{
// 异步
});
GCD的使用:
- (void)gcdTest {
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"---begin");
dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
//任务一
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2];
NSLog(@"1---%@",[NSThread currentThread]);
} });
dispatch_sync(queue, ^{
//任务二
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2];
NSLog(@"2---%@",[NSThread currentThread]);
} });
dispatch_sync(queue, ^{
//任务三
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2];
NSLog(@"3---%@",[NSThread currentThread]);
} });
NSLog(@"---end");
}
(1)同步+并发,同步+串行
2019-02-15 20:30:13.134436+0800 GCD[11324:226430] currentThread---<NSThread: 0x6000019dafc0>{number = 1, name = main}
2019-02-15 20:30:13.134613+0800 GCD[11324:226430]---begin
2019-02-15 20:30:15.135160+0800 GCD[11324:226430] 1---<NSThread: 0x6000019dafc0>{number = 1, name = main}
2019-02-15 20:30:17.136650+0800 GCD[11324:226430] 1---<NSThread: 0x6000019dafc0>{number = 1, name = main}
2019-02-15 20:30:19.138189+0800 GCD[11324:226430] 2---<NSThread: 0x6000019dafc0>{number = 1, name = main}
2019-02-15 20:30:21.139706+0800 GCD[11324:226430] 2---<NSThread: 0x6000019dafc0>{number = 1, name = main}
2019-02-15 20:30:23.141219+0800 GCD[11324:226430] 3---<NSThread: 0x6000019dafc0>{number = 1, name = main}
2019-02-15 20:30:25.142714+0800 GCD[11324:226430] 3---<NSThread: 0x6000019dafc0>{number = 1, name = main}
2019-02-15 20:30:25.142980+0800 GCD[11324:226430]---end
这两种结果都一样,虽然并发队列有执行多线程的能力,但是由于是同步执行,只会在单一线程中执行,所以会在同一线程中依次执行完任务后再添加下一个任务
(3)异步+串行
2019-02-15 20:42:52.282373+0800 GCD[11506:241315] currentThread---<NSThread: 0x600003b93640>{number = 1, name = main}
2019-02-15 20:42:52.282539+0800 GCD[11506:241315] ---begin
2019-02-15 20:42:52.282674+0800 GCD[11506:241315] ---end
2019-02-15 20:42:54.286775+0800 GCD[11506:241365] 1---<NSThread: 0x600003bf1f00>{number = 3, name = (null)}
2019-02-15 20:42:56.287246+0800 GCD[11506:241365] 1---<NSThread: 0x600003bf1f00>{number = 3, name = (null)}
2019-02-15 20:42:58.292730+0800 GCD[11506:241365] 2---<NSThread: 0x600003bf1f00>{number = 3, name = (null)}
2019-02-15 20:43:00.293612+0800 GCD[11506:241365] 2---<NSThread: 0x600003bf1f00>{number = 3, name = (null)}
2019-02-15 20:43:02.299173+0800 GCD[11506:241365] 3---<NSThread: 0x600003bf1f00>{number = 3, name = (null)}
2019-02-15 20:43:04.300262+0800 GCD[11506:241365] 3---<NSThread: 0x600003bf1f00>{number = 3, name = (null)}
当遇到异步执行时,会创建一个新的线程,主线程继续向下执行,新线程执行添加的任务,新线程是串行队列,也是按顺执行
(4)异步+并行
2019-02-15 20:47:46.005210+0800 GCD[11582:247831] currentThread---<NSThread: 0x600000071400>{number = 1, name = main}
2019-02-15 20:47:46.005361+0800 GCD[11582:247831] ---begin
2019-02-15 20:47:46.005466+0800 GCD[11582:247831] ---end
2019-02-15 20:47:48.005804+0800 GCD[11582:247876] 2---<NSThread: 0x600000012940>{number = 4, name = (null)}
2019-02-15 20:47:48.005805+0800 GCD[11582:247877] 1---<NSThread: 0x60000001c0c0>{number = 3, name = (null)}
2019-02-15 20:47:48.005804+0800 GCD[11582:247879] 3---<NSThread: 0x60000001c100>{number = 5, name = (null)}
2019-02-15 20:47:50.008365+0800 GCD[11582:247876] 2---<NSThread: 0x600000012940>{number = 4, name = (null)}
2019-02-15 20:47:50.008365+0800 GCD[11582:247879] 3---<NSThread: 0x60000001c100>{number = 5, name = (null)}
2019-02-15 20:47:50.008387+0800 GCD[11582:247877] 1---<NSThread: 0x60000001c0c0>{number = 3, name = (null)}
这种情况就是主线程在执行,同时其他开辟多个线程同时执行
(5)同步+主队列
这种情况下,如果调用的函数在其他线程执行,和同步+串行情况相同,但是在主线程下执行,打印完begin之后会死锁,程序崩溃。因为函数在主队列,他会执行完函数再调用添加进来的任务,但是任务不执行完就没法走完函数,有点像循环引用,A引用B,B引用A,双方都等对方执行完再执行,这种情况要避免。
GCD的其他方法
(1)栅栏方法:?????dispatch_barrier_async
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"---begin");
dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
//任务一
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2];
NSLog(@"1---%@",[NSThread currentThread]);
} });
dispatch_async(queue, ^{
//任务二
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2];
NSLog(@"2---%@",[NSThread currentThread]);
} });
dispatch_barrier_async(queue, ^{
NSLog(@"barrier---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
//任务三
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2];
NSLog(@"3---%@",[NSThread currentThread]);
} });
NSLog(@"---end");
结果
2019-02-16 00:02:50.327233+0800 GCD[13192:337727] currentThread---<NSThread: 0x6000010e2900>{number = 1, name = main}
2019-02-16 00:02:50.327496+0800 GCD[13192:337727] ---begin
2019-02-16 00:02:50.327658+0800 GCD[13192:337727] ---end
2019-02-16 00:02:52.333127+0800 GCD[13192:337760] 1---<NSThread: 0x600001087480>{number = 3, name = (null)}
2019-02-16 00:02:52.333147+0800 GCD[13192:337762] 2---<NSThread: 0x600001087500>{number = 4, name = (null)}
2019-02-16 00:02:54.335308+0800 GCD[13192:337760] 1---<NSThread: 0x600001087480>{number = 3, name = (null)}
2019-02-16 00:02:54.335321+0800 GCD[13192:337762] 2---<NSThread: 0x600001087500>{number = 4, name = (null)}
2019-02-16 00:02:54.335729+0800 GCD[13192:337762] barrier---<NSThread: 0x600001087500>{number = 4, name = (null)}
2019-02-16 00:02:56.336141+0800 GCD[13192:337762] 3---<NSThread: 0x600001087500>{number = 4, name = (null)}
2019-02-16 00:02:58.340345+0800 GCD[13192:337762] 3---<NSThread: 0x600001087500>{number = 4, name = (null)}
可以看出,栅栏函数能隔开加载到队列里的任务,未加载到队列里的任务无效。在栅栏函数前面的任务全部执行完后,执行栅栏函数,然后执行栅栏函数后的任务。
(2)延时执行方法:dispatch_after
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), queue, ^{
NSLog(@"after---%@",[NSThread currentThread]);
});
顾名思义,就是延迟执行,上面代码是延迟3s,注意,这个延迟是3s后把任务加入队列,不是3s后就执行,如果你加入的是串行队列,3s后加入队列,但是前面的任务需要10s才执行完,那你得到结果的时间是13s。
(3)只执行一次:dispatch_once
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//任务
});
该函数能保证代码在程序运行过程中只执行一次,并且即使在多线程的环境下,也能保证里面的代码线程安全。
(4)快速迭代方法:dispatch_apply
NSLog(@"---begin");
dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"---end");
2019-02-16 00:32:36.142181+0800 GCD[13675:371444] ---begin
2019-02-16 00:32:36.142501+0800 GCD[13675:371494] 2---<NSThread: 0x6000019e0b00>{number = 4, name = (null)}
2019-02-16 00:32:36.142526+0800 GCD[13675:371495] 1---<NSThread: 0x6000019dfec0>{number = 3, name = (null)}
2019-02-16 00:32:36.142529+0800 GCD[13675:371444] 0---<NSThread: 0x600001985400>{number = 1, name = main}
2019-02-16 00:32:36.142537+0800 GCD[13675:371493] 3---<NSThread: 0x6000019e0a80>{number = 5, name = (null)}
2019-02-16 00:32:36.142644+0800 GCD[13675:371494] 4---<NSThread: 0x6000019e0b00>{number = 4, name = (null)}
2019-02-16 00:32:36.142678+0800 GCD[13675:371495] 5---<NSThread: 0x6000019dfec0>{number = 3, name = (null)}
2019-02-16 00:32:36.142693+0800 GCD[13675:371444] 6---<NSThread: 0x600001985400>{number = 1, name = main}
2019-02-16 00:32:36.142735+0800 GCD[13675:371493] 7---<NSThread: 0x6000019e0a80>{number = 5, name = (null)}
2019-02-16 00:32:36.142786+0800 GCD[13675:371494] 8---<NSThread: 0x6000019e0b00>{number = 4, name = (null)}
2019-02-16 00:32:36.142807+0800 GCD[13675:371495] 9---<NSThread: 0x6000019dfec0>{number = 3, name = (null)}
2019-02-16 00:32:36.143608+0800 GCD[13675:371444] ---end
dispatch_apply有点像for循环,如果是串行队列,和for差不多,依次N次任务加入到队列中,顺序执行,如果是并行队列,则是并发执行任务,从结果中看出,这个函数会调用主线程,不知道会不会在某些情况下造成阻塞。这个函数本身是同步的,所以会执行完全部任务,再执行下面的任务。
(5)dispatch_group
1、group基础
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2];
NSLog(@"1---%@",[NSThread currentThread]);
};
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2];
NSLog(@"2---%@",[NSThread currentThread]);
};
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2];
NSLog(@"3---%@",[NSThread currentThread]);
};
NSLog(@"group---end");
});
2019-02-16 01:16:04.203743+0800 GCD[14254:412235] currentThread---<NSThread: 0x600001b9a900>{number = 1, name = main}
2019-02-16 01:16:04.203910+0800 GCD[14254:412235] group---begin
2019-02-16 01:16:06.205824+0800 GCD[14254:412362] 1---<NSThread: 0x600001bf5000>{number = 3, name = (null)}
2019-02-16 01:16:06.205845+0800 GCD[14254:412365] 2---<NSThread: 0x600001bf5040>{number = 4, name = (null)}
2019-02-16 01:16:08.211355+0800 GCD[14254:412362] 1---<NSThread: 0x600001bf5000>{number = 3, name = (null)}
2019-02-16 01:16:08.211356+0800 GCD[14254:412365] 2---<NSThread: 0x600001bf5040>{number = 4, name = (null)}
2019-02-16 01:16:10.212927+0800 GCD[14254:412235] 3---<NSThread: 0x600001b9a900>{number = 1, name = main}
2019-02-16 01:16:12.214396+0800 GCD[14254:412235] 3---<NSThread: 0x600001b9a900>{number = 1, name = main}
2019-02-16 01:16:12.214740+0800 GCD[14254:412235] group---end
任务先加入到队列,然后队列加入到队列组,当队列组中的任务执行完成后,会执行dispatch_group_notify函数任务
2、wait
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
阻塞当前的线程,等到指定group中的任务执行完毕后再继续执行。
3、group_leave,group_enter
dispatch_group_enter ?????????标志着一个任务追加到group?,相当于group中未执行完毕的任务数+1
dispatch_group_leave ?????????标志着一个任务离开了group?,相当于group中未执行完毕的任务数-1
当group中未完成数为0的时候,才会执行notify和wait函数,这里有点像是引用计数。
(6)信号量dispatch_semaphore
dispatch_semaphore_create?:创建一个信号量
dispatch_semaphore_signal?:发送一个信号量,信号量+1
dispatch_semaphore_wait?:当线程信号量为0时阻塞线程,直到有新的信号量添加,当通过这个方法时,信号量的值-1
应用:保证线程安全,为线程加锁,或者保持线程同步,将异步执行任务转换成同步
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"2---%@",[NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
NSLog(@"1---%@",[NSThread currentThread]);
dispatch_semaphore_wait(semaphore,time);//DISPATCH_TIME_FOREVER没有废弃时间
NSLog(@"3---%@",[NSThread currentThread]);
2019-02-16 15:35:58.372359+0800 GCD[17462:642975] 1---<NSThread: 0x600002a193c0>{number = 1, name = main}
2019-02-16 15:36:00.375787+0800 GCD[17462:643022] 2---<NSThread: 0x600002a718c0>{number = 3, name = (null)}
2019-02-16 15:36:00.376150+0800 GCD[17462:642975] 3---<NSThread: 0x600002a193c0>{number = 1, name = main}
创建一个信号量,由于线程任务是异步执行,所以开辟了一个新线程去执行,主线程继续执行下面的打印任务,当遇到dispatch_semaphore_wait时,信号量为0,阻塞主线程。新线程由于有耗时操作,2s后执行完任务,打印并且使信号量+1,这时主线程中信号量为1,所以通过了wait方法,继续执行下面的打印。这里说下dispatch_semaphore_wait的第二个参数,是指等待时间,如果超出这个时间,也会继续向下运行,但是这个信号量就已经废弃了,下面再遇到dispatch_semaphore_wait方法也不会起作用。