IOS多线程--GCD
ios多线程--GCD(Grand Central Dispatch)
1.GCD的好处
- GCD可以用于多核的并行运算
- GCD会利用更多的CPU内核(比如双核,四核)
- GCD会自动管理线程的生命周期(创建线程,调度任务,销毁线程)
- 程序员只需要告诉GCD要执行什么任务,不需要编写任何线程管理代码
2.同步执行和异步执行
同步执行(sync---synchronize)
- 同步添加任务到指定的队列中在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后在继续执行
- 只能在当前线程中执行任务,不具备开启新线程的能力
异步执行(async---asynchronous)
- 异步添加任务到指定的队列当中去,它不会做任何的等待,可以直接执行任务
- 可以在新的线程中执行任务,具备开启新线程的能力
举例子
同步执行:在一个安检口,人们排队过安检,前一个人安检完了,下一个人才安检
异步执行:在机场里有许多的安检口,人们可以同时在不同的安检口过安检
注意:异步执行(async)具有开启新线程的能力,但是它不一定开启新线程
3.串行队列和并发队列
串行队列:只开启一个线程,一个任务一个任务的执行
并发队列:可以开启多个线程,并且同时执行任务
注意:并发队列的并发功能只有在异步(dispatch_async)方法下才生效
4.如何使用GCD
1.创建一个队列
dispatch_queue_creat
方法来创建队列,传入两个参数
- 参数一表示队列的唯一标识符,可为nil
- 参数二是用来识别是串行队列还是并发队列
DISPATCH_QUEUE_SERIAL //(串行队列)
DISPATCH_QUEUE_CONCURRENT //(并发队列)
主队列
对于串行队列,GCD默认提供了主队列(Main Dispatch Queue)
- 所有放在主队列的任务,都会被放在主线程中执行
- 可以使用dispathc_get_main_queue()方法获得主队列
- 注意:主队列其实不特殊,主队列的本质就是一个普通的串行队列,只是因为默认情况下,当前代码是放在主队列当中的,然后主队列中的代码,有的会放到主线程中去执行,所以才造成了主队列特殊的情况
dispatch_queue t queue = dispatch_get_main_queue();
全局并发队列
对于并发队列,GCD默认提供了全局并发队列(Global Dispatch Queue)
可以使用dispatch_get_global_queue方法来获取全局并发队列。需要传入两个参数,第一个参数为优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT来表示,第二个参数暂时没用,用0即可
diapatch_queue_t queue = dispatch_get_gloabl_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
2.创建任务
GCD提供了同步执行任务的创建方法dispatch_sync和异步执行任务的创建方法dispatch_async
//同步执行任务的创建方法
dispatch_sync(queue,^{
//这里放同步执行代码
});
//异步执行任务的创建方法
dispatch_async(queue,^{
//这里放异步执行代码
});
5.主线程
1.主线程
区别 | 并发队列 | 串行队列 | 主队列 |
---|---|---|---|
同步(sync) | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 | 死锁 |
异步(async) | 有开启新线程,并发执行任务 | 有开启新线程(1条),串行执行任务 | 没有开启新线程,串行执行任务 |
在主线程中调用主队列 + 同步执行会导致死锁问题,因为主队列中追加的同步任务和主线程本身的任务两者间互相等待,阻塞了主队列,最终导致死锁。
注意:在子线程调用主队列 + 同步执行,不会阻塞主队列,也不会造成死锁问题。最终结果是:不会开启新线程,串行执行任务。
6.六种组合方式
- 同步执行 + 并发队列
- 异步执行 + 并发队列
- 同步执行 + 串行队列
- 异步执行 + 串行队列
- 同步执行 + 主队列
- 异步执行 + 主队列
1.同步执行 + 并发队列
在当前任务中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
- 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)
- 任务按顺序执行
注意:虽然是并发队列可以开启多个线程,并同时执行多个任务。但是因为是同步执行,本身不能创建新线程,只有当前一个线程,所以也就不能存在并发。而当前线程只有等待当前队列中正在执行的任务执行完毕后,才能继续执行下面的操作。所以任务只能一个个被执行,不能同时执行。通俗的讲,并行队列能开多线程,同步执行限制了它,它没开成。
2.异步执行 + 并发队列
开启多个线程,任务交替(同时)执行
- 除了当前的线程(主线程),系统还会开启别的子线程,并且任务是交替进行的
异步执行具备开启新线程的能力,且并发队列可开启多个线程,同时执行多个任务
3.同步执行 + 串行队列
不会开启新的线程,在当前线程执行任务。任务是串行的,执行完一个任务再执行下一个任务
- 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(同步执行不具备开启新线程的能力)
- 任务按顺序执行(串行队列每次只有一个任务被执行)
4.异步执行 + 串行队列
会开启新的线程,但是因为是串行队列,所以执行完一个任务,再执行下一个任务
- 开启一条新的线程(异步执行具备开启新线程的能力,串行队列只开启一个线程)
- 异步执行不做任何等待,直接执行任务
- 任务是按照顺序执行,串行队列每次只有一个任务被执行,任务一个接一个按顺序执行
5.同步执行 + 主队列
GCD默认提供的主队列就是串行队列,它是绑定在主线程的队列
- 默认情况下,平常所写的代码是直接放进主队列中的
- 所有放在主队列中的任务,都会放到主线程中执行
- 可使用dispatch_get_main_queue()获得主队列
同步执行 + 主队列在不同线程中调用结果也是不一样的,在主线程中调用会发生死锁问题,而在其他线程中调用则不会
在主线程中调用 同步执行 + 主队列
/**
* 同步执行 + 主队列
* 特点(主线程调用):互等卡主不执行。
* 特点(其他线程调用):不会开启新线程,执行完一个任务,再执行下一个任务。
*/
- (void)syncMain {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"syncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"syncMain---end");
}
输出:syncMain---begin
造成死锁的原因:我们在主线程中执行syncMain方法,相当于把syncMain任务放到了主线程的队列中,而同步执行会等待当前队列中任务执行完毕,才会继续执行。所以当我们把任务1追加进主队列中,任务1就在等待主线程执行完syncMain任务,而syncMain任务需要等待任务1执行完毕,才能接着执行。所以syncMain和任务1都在等对方执行完毕,所以就死锁了
解释什么是死锁
面试时,老板对面试者说,你今天把死锁说明白了,我就录取你,面试者说,你让我进入公司了,我就把死锁和你说明白,就产生了死锁
在其他线程中使用同步执行 + 主队列 不会造成死锁
- 所有任务都是在主线程中执行的,但主线程并不是当前使用的线程,并且没有开启新的线程(所有放在主队列中的任务,都会放在主线程中执行)
- 任务是按顺序执行的(主队列是串行队列)
6.异步执行 + 主队列
只在主线程中执行任务,执行完一个任务,再执行下一个任务
- 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(虽然异步执行具备开启线程的能力,但是主队列,所有任务都在主队列中)
- 任务 直接执行
- 任务是按顺序执行的(因为主队列是串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行
7.GCD线程间的通信
我们在主线程中进行UI刷新,把一些耗时的操作放在其他线程,例如图片的下载,文件的上传等等。当我们在其他线程完成耗时操作,需要回到主线程程时,就完成了线程间的通信。
/**
* 线程间通信
*/
- (void)communication {
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 异步追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
// 回到主线程
dispatch_async(mainQueue, ^{
// 追加在主线程中执行的任务
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
});
}
8.GCD的其他方法
1.GCD的栅栏方法:dispatch_barrier_async
有时需要异步执行两组操作,而且是第一组执行完再执行第二组,就可以使用栅栏方法,各个组里的执行顺序不一定
2.GCD的延时执行方法:dispatch_after
当有的任务需要在几秒后再执行时,就可以使用dispatch_after方法来实现
注意:dispatch_after并不是在指定时间之后再开始执行处理,而是在指定时间后,将任务追加到主队列中
9.一些例子
1)死锁的情况
- (void) testDemo {
dispatch_queue_t queue = dispatch_queue_creat("mgd",DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
输出顺序:1 5 2 死锁
解释:串行队列,打印1,异步函数,执行5,在队列中加入任务2,再加入一个任务块,再加入任务4,但是加入的任务块是同步的,会产生堵塞,堵塞任务4,执行任务块时,会往队列里再加入一个任务3,所以我们在执行任务块时,会先执行任务3,但是在队列中,任务3比任务4后面进入,根据队列的先进先出原则,会导致,任务3要等任务4执行完再执行,而任务4要等任务块执行完再执行,而任务块的任务是执行任务3,产生死锁
2)并发队列+异步执行
- (void)testDemo {
dispatch_queue_t queue = dispatch_queue_creat("mgd",DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
输出顺序:1 5 2 4 3
解释:并发队列,程序由上到下执行时,先打印1,经过异步函数,它会延迟执行,但不会堵塞,所以程序不会等待这个异步函数执行,所以打印5,然后再执行大的异步函数代码块,打印2,和上面一样,不会等待,所以打印4,最后打印3。
- (void) testDemo {
dispatch_queue_t queue = dispatch_queue_creat("mgd",DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1");
});
dispatch_async(queue, ^{
NSLog(@"2");
});
dispatch_sync(queue, ^{
NSLog(@"3");
});
//*************
NSLog(@"0");
dispatch_async(queue, ^{
NSLog(@"7");
});
dispatch_async(queue, ^{
NSLog(@"8");
});
dispatch_async(queue, ^{
NSLog(@"9");
});
}
输出顺序:1 2 3(无序实现,可能是2 3 1)0 (0的位置是确定的) 7 8 9(7 8 9也是无序的,可能是8 9 7)
解释:并发队列,异步执行,任务1,任务2没有顺序,任务3也没有顺序,同步执行堵塞的是同步执行以下的代码,所以在任务3完成之前,分割线以下的任务无法执行,前三个任务执行完之后,执行任务0,接着,同上,任务7,8,9顺序也不一定。