IOS多线程—GCD由浅入深总结
注意几个名词:
同步:不会开启子线程, 而且会阻塞当前线程
异步:不会阻塞当前线程, 且具备开启线程的能力(不一定开启)
任务:block里面的代码块
串行队列:只会开启一个子线程
并行队列:可以开启多个子线程,但是不一定一定开启线程,因为开启一个线程会:消耗CPU资源、占内存(子线程512kb,主线程1M)能开启几个线程取决于内核,一般开启3-6个就可以了
主队列:异步添加任务必须等到主线程中的任务执行完才执行,就是一个串行队列,但是在主队列中添加的异步任务不会开启子线程
第一部分:基础
一、同步串行
//同步串行
/*
同步: 看到这两个字首先想到的是不会开启子线程,而且会阻塞当前线程
*/
- (void)syncSerial{
NSLog(@"1111");
dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"2222");
});
dispatch_sync(queue, ^{
NSLog(@"3333");
});
dispatch_sync(queue, ^{
NSLog(@"4444");
});
NSLog(@"5555");
}
执行结果:
image.png二、异步串行
//异步串行
/*
异步: 要想到的是不会阻塞当前线程,且具备开启线程的能力(不一定会创建子线程,例如在串行队列中,只会创建一条子线程)
*/
- (void)asyncSerial{
NSLog(@"1111");
dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"2222");
});
dispatch_async(queue, ^{
NSLog(@"3333");
});
dispatch_async(queue, ^{
NSLog(@"4444");
});
NSLog(@"5555");
}
执行结果:
image.png三、异步并行
//异步并行
- (void)asyncConcurrent{
NSLog(@"1111");
dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"2222");
});
dispatch_async(queue, ^{
NSLog(@"3333");
});
dispatch_async(queue, ^{
NSLog(@"4444");
});
NSLog(@"5555");
}
执行结果:
image.png四、同步并行
//同步并行
- (void)syncConcurrent{
NSLog(@"1111");
dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"2222");
});
dispatch_sync(queue, ^{
NSLog(@"3333");
});
dispatch_sync(queue, ^{
NSLog(@"4444");
});
NSLog(@"5555");
}
执行结果:
image.png第二部分:嵌套
一、同步并行嵌套异步任务
//同步并行嵌套异步任务
- (void)syncConcurrentAsync{
NSLog(@"1111");
dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"2222");
dispatch_async(queue, ^{
sleep(2);
NSLog(@"3333");
});
NSLog(@"4444");
});
NSLog(@"5555");
}
执行结果:
image.png结果分析:
1、先执行11111
2、遇到sync,同步任务,不会开启子线程,而且会阻塞当前线程,所以会执行2222
3、遇到aync,异步任务会开启子线程,不会阻塞当前线程,所以 执行一下3333,但是不需要3333这个异步任务执行完(不会阻塞当前线程) 就可以执行4444
4、执行完4444后,因为3333延迟2秒,所以会执行5555(如果不延迟2秒的话,可能会先执行3333也可能会先执行5555)
5、2秒后执行3333
二、同步串行嵌套异步任务
//同步串行嵌套异步任务
- (void)syncSerialAsync{
NSLog(@"1111");
dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
//block1:任务1
NSLog(@"2222");
dispatch_async(queue, ^{
//block2:任务2
NSLog(@"3333");
});
sleep(2);
NSLog(@"4444");
});
sleep(2);
NSLog(@"5555");
}
执行结果:
image.png结果分析:
1、在串行队列中,任务顺序执行,必修等一个任务完成才能执行下一个任务,代码中sync包含两个任务:block1(2222,3333,4444)和block2(3333)
2、首先执行1111
3、遇到sync:同步任务,且在串行队列中有两个任务,顺序为block1和block2,所以要先执行完block1才能执行block2,所以先执行2222
4、遇到block1中async:异步任务,开启一个线程,不会阻塞当前线程,所以执行sleep和4444,因为3333是在block2中,所以会要把sleep和4444执行完(谨记sleep和4444在block1中),才会执行block2中的3333
5、两个任务都执行完后,执行sleep和5555
三、异步串行嵌套同步任务
//异步串行嵌套同步任务
- (void)asyncSerialSync{
NSLog(@"1111");
dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
//block1:任务1
NSLog(@"2222");
dispatch_sync(queue, ^{
//block2:任务2
NSLog(@"3333");
});
NSLog(@"4444");
});
NSLog(@"5555");
}
执行结果:死锁
image.png结果分析:
1、先执行1111
2、遇到aync:异步任务,开启子线程,不会阻塞当前线程,所以执行5555
3、在aync中先执行2222
4、遇到sync:同步任务,不会开启新的子线程,且阻塞当前线程(2步骤中开启的子线程),又因为任务block1和block2在串行队列中,所以必须要等到任务block1(4444)执行完才能执行任务block2(3333),综合刚才说的block2(3333)阻塞这个子线程(即必须等到block2(3333)完成才能继续执行block1中的(4444)),进而造成死锁。
四、主队列中添加异步任务
//主队列中添加异步任务
- (void)main{
NSLog(@"1111");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"2222");
});
sleep(2);
NSLog(@"3333");
}
执行结果:
image.png结果分析:
1、主队列中异步添加任务必须等到主线程中的任务执行完才执行
2、主队列就是一个串行队列,但是在主队列中添加的异步任务不会开启子线程
五、主队列中添加同步任务
//主队列中添加同步任务
- (void)main1{
//任务1
NSLog(@"1111");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
//block任务2
NSLog(@"2222");
});
NSLog(@"3333");
}
执行结果:死锁
image.png结果分析:
1、在主线程中有两个任务:main1和block,先执行1111
2、遇到sync:同步任务,阻塞主线程
3、但是要想执行任务2(2222)必须要先执行完主线程中的任务,即main1(1111和3333)造成main1和block相互阻塞。
第三部分:常用api
一、dispatch_apply
- (void)apply{
dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"index=%ld线程:%@", index, [NSThread currentThread]);
});
NSLog(@"end");
}
执行结果:
image.png结果分析:
dispatch_apply类似一个for循环,会在指定的队列中中运行block任务n次,如果队列是并发队列,则会并发执行block任务。
dispatch_apply在串行队列中按照顺序执行,完全没有意义。在并发队列中创建了N个任务,但并非所有任务都开辟线程,也有在主线程中完成的。输出end,因为dispatch_apply函数会等待所有的处理结束,才会向下执行。
二、dispatch_group_t
- (void)group{
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"1111");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"2222");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"3333");
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"4444");
});
}
执行结果
image.png结果分析:
在group中的任务异步执行。等全部执行完毕后,通过dispatch_group_notify再执行后面代码。
三、比较dispatch_apply和dispatch_group_t
通过前两个api讲述可以看到其功能很相似。
1、我在dispatch_apply中添加异步任务
- (void)apply{
dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(20, queue, ^(size_t index) {
dispatch_async(queue, ^{
NSLog(@"index=%ld线程:%@", index, [NSThread currentThread]);
});
});
NSLog(@"end");
}
执行结果:
image.png结果分析:
可以看出先执行end,在dispatch_apply中的异步任务均是在子线程中完成。
2、在dispatch_group_t中同样添加异步任务
- (void)group{
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1111");
});
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2222");
});
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"3333");
});
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"4444");
});
NSLog(@"5555");
}
执行结果:
image.png结果分析:
可以看出,同dispatch_apply一样,也不能保证group中全部完成再执行4444。
3、但是dispatch_group_t可以这么做来解决
- (void)group{
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1111");
dispatch_group_leave(group);
});
});
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2222");
});
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"3333");
dispatch_group_leave(group);
});
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"4444");
});
NSLog(@"5555");
}
执行结果:
image.png第四部分:线程锁(线程安全)
线程不安全:内存数据被多个线程读取之后,出现的结果不可预见。
线程锁:确保在读取数据时只有一条线程再操作。
这里总结三种锁:synchronized、nslock、dispatch_semaphore_t、nonatomic和atomic的区别
先看下边代码
- (void)synchronized{
dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1111");
sleep(2);
NSLog(@"2222");
});
dispatch_async(queue, ^{
NSLog(@"3333");
sleep(2);
NSLog(@"4444");
});
}
执行顺序:
image.png出现线程不安全,因此需要加锁。
1、synchronized
- (void)synchronized{
dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
@synchronized (self){
NSLog(@"1111");
sleep(2);
NSLog(@"2222");
}
});
dispatch_async(queue, ^{
@synchronized (self){
NSLog(@"3333");
sleep(2);
NSLog(@"4444");
}
});
NSLog(@"5555");
}
执行结果:
image.png结果分析:
等到一个线程完成,才会执行另一个线程。
2、dispatch_semaphore_t
/*
信号量:控制我们的线程并发数
*/
- (void)semaphore{
//线程并发数设为1,只有一个线程在走
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"1111");
sleep(2);
NSLog(@"2222");
dispatch_semaphore_signal(semaphore);
});
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"3333");
sleep(2);
NSLog(@"4444");
dispatch_semaphore_signal(semaphore);
});
NSLog(@"5555");
}
执行结果:
image.png3、项目中为什么用nonatomic不用atomic
nonatomic:不安全
atomic:加锁+耗性能
//有两个属性,分别设置为nonatomic和atomic
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (nonatomic, strong) NSString *name;
@property (atomic, assign) int number;
@end
. 10000个异步任务,修改name属性的值
- (void)nonatomic{
for (NSInteger i = 0; i < 10000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.name = [NSString stringWithFormat:@"name:%ld", i];
});
}
}
执行结果:崩溃,崩溃原因是在子线程Thread8上,对象释放了。
image.png结果分析:
1、在MRC模式下,属性name的set方法如下:
-(void)setName:(NSString *)name{
if (_name != name) {
[_name release];
[name retain];
_name = name;
}
}
2、虽然在ARC模式下不用写其set方法,但是在底层还是会走到这里
3、因为是多线程,且没有加锁保护,所以在一个线程走到[_name release]后,可能在另一个线程又一次去释放,这时候造成崩溃。
4、把name属性的nonatomic改成atomic就不会崩溃了,因为atomic加锁了,是安全的。
. 接着上步说用atomic就安全了,再举个例子
number属性使用atomic修饰的
- (void)atomic{
_number = 0;
dispatch_apply(10000, dispatch_get_global_queue(0, 0), ^(size_t index) {
self->_number ++;
});
NSLog(@"_number:%d", _number);
}
执行结果:执行结果并不是10000,而且每次运行结果都不一样,即运行结果不可预见。
image.png结果分析:
_number++等价于
int temp = _number+1;
_number = temp;
虽然atomic保证了number属性线程安全了,但是并不能保证temp变量的线程安全,又因为是多线程的,所以有可能同时执行多次 int temp = _number+1;才执行一次 _number = temp;导致结果越来越小,而且结果不可预知。
这时候就可以知道为什么不用atomic了:因为atomic会耗性能,而且大部分情况下并不会保证线程安全。
什么时候可以用atomic呢:在最简单的,只有一个set时,简单的读写实例变量。
UIKIT不需要使用atomic:因为UIKIT是在主线程做的,不存在线程安全问题。
4、NSLock
解决上步的问题
- (void)atomic{
_number = 0;
NSLock *lock = [[NSLock alloc] init];
dispatch_apply(10000, dispatch_get_global_queue(0, 0), ^(size_t index) {
[lock lock];
self->_number ++;
[lock unlock];
});
NSLog(@"_number:%d", _number);
}
执行结果:
image.png