养成好习惯,把学过的东西都留一手,如有错请指示
基本概念
-
进程
进程是指系统中正在运行的一个应用程序,针对于iOS来说,就是开启了一个app,这个app的运行就是一个进程
-
线程
一个进程要想执行任务,那么就必须得有线程,一个进程至少得有一个线程
-
多线程的优缺点
优点:
1.能适当的提高程序的运行效率
2.能适当的提高cpu的利用率
缺点
1.开启线程需要栈和寄存器等内存消耗,默认的一条线程占用栈去512kb
2.线程过多会导致CPU在线程上的消耗比较大
3.线程过多,程序设计就复杂了
-
并发与并行
并发
并发:描述的是多个任务同时发生,都需要被处理,这是一种现象,侧重点在发生
并行
并行:指的是一种技术,一个同时处理多个任务的技术,侧重点在运行
总结
我们说的多线程,其实就是采取了并行技术,从而提高执行效率,因为有个多个线程,所以计算机的多个cpu可以同时工作,处理不同线程内的指令,但是对于单核的cpu而言,多线程其实cpu在多个线程不停的调度,并发是一种现象,面对这一现象,我们首先创建多个线程,真正加快程序运行速度的是并行技术,也就是让多个cpu同时工作,而多线程是为了让cpu同时工作成为可能,而对于单核的cpu,就是让cpu能在各个线程调度称为可能
NSOperation & NSOperationQueue
简介:把要执行的任务封装到一个操作对象(operation object),并将操作对象放入操作队列(nsoperationqueue),然后系统就会自动在执行任务。至于同步还是异步、串行还是并行请继续往下看
-
NSOperation的两个子类
1.NSInvocationOperation
2.NSBlockOperation
GCD
-
同步任务与异步任务
任务:即操作,在gcd当中就是一个block,任务执行的两种方式:同步执行 和 异步执行
同步任务(sync)和异步任务(async)的主要区别在于会不会阻塞当前线程,直到block中的任务执行完毕
同步任务(sync):它会阻塞当前线程并等待block中的任务执行完毕,然后当前线程才会继续往下运行
异步任务(async):当前线程会直接往下执行,不用等着当前任务执行完,并不会阻塞当前线程
同步:只能在当前线程执行任务,不具备开启新线程的能力
异步:可以在新的线程执行任务,具备开启新线程的能力
-
队列
-
串行队列:先进先出队列,每次只执行一个任务,线程任务按先后顺序逐个执行(需要等待队列里面前面的任务执行完之后再执行新的任务)
-
-
并发队列:先进先出队列,不过可以形成多个任务并发,也就是说,虽然也是FIFO,但是不同的是,它取出来一个任务就放到别的线程,然后再取出来一个放到别的线程,动作很快,看起来所有的任务都是一起执行的,不过gcd会根据系统资源控制并行的数量,多个任务按添加顺序一起开始执行(不用等待前面的任务执行完再执行新的任务),但是添加间隔往往忽略不计,所以看着像是一起执行的
-
主队列:这个是一个特殊的串行队列,队列中的每个任务一定执行在主线程中,如果主线程上有任务在执行,主队列就不会调度任务
-
总结
关于同步异步、串行并行和线程的关系,下面通过一个表格来总结
可以看到,同步方法不一定在本线程,异步方法方法也不一定新开线程(考虑主队列)。
- 开不开线程,取决于执行任务的函数,同步不开,异步才能开
- 开几条线程,取决于队列,串行开一条,并发可以开多条(异步)
队列是负责调度任务的,同步异步负责执行任务
串行队列和并发队列的区别:会不会阻塞当前队列(阻塞的意思:队列里的任务不用执行完,就可以拿出下一个任务)
同步和异步的区别:会不会阻塞当前线程(阻塞的意思:不用等待当前的任务是否完成,就可以执行下一个任务)
串行队列 并发队列 demo
-(void)gcdDemo8{
/*
全局队列 & 并发队列
1> 名称,并发队列取名字,适合于企业开发跟踪错误
2> release,在MRC 并发队列 需要使用的
dispatch_release(q);//ARC 情况下不需要release !
全局队列 & 串行队列
全局队列: 并发,能够调度多个线程,执行效率高
- 费电
串行队列:一个一个执行,执行效率低
- 省点
判断依据:用户上网方式
- WIFI : 可以多开线程
- 流量 : 尽量少开线程
*/
//1.队列
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i< 10; i++) {
NSLog(@" %d",i);
dispatch_async(q, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
NSLog(@"come here");
}
//MARK: 全局队列 (本质上并发队列)
-(void)gcdDemo7{
//全局队列
/* 参数
1. 涉及到系统适配
iOS 8 服务质量
QOS_CLASS_USER_INTERACTIVE 用户交互(希望线程快速被执行,不要用好使的操作)
QOS_CLASS_USER_INITIATED 用户需要的(同样不要使用耗时操作)
QOS_CLASS_DEFAULT 默认的(给系统来重置队列的)
QOS_CLASS_UTILITY 使用工具(用来做耗时操作)
QOS_CLASS_BACKGROUND 后台
QOS_CLASS_UNSPECIFIED 没有指定优先级
iOS 7 调度的优先级
- DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
- DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
- DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
- DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级
提示:尤其不要选择BACKGROUND 优先级,服务质量,线程执行会慢到令人发指!!!
2. 为未来使用的一个保留,现在始终给0.
老项目中,一般还是没有淘汰iOS 7 ,没法使用服务质量
*/
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
for (int i = 0; i< 10; i++) {
dispatch_async(q, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
NSLog(@"come here");
}
#pragma mark - <同步任务>
//MARK : 增强版同步任务
// 可以队列调度多个任务前,指定一个同步任务,让所有的异步任务,等待同步任务执行完成,这就是依赖关系
// - 同步任务,会造成一个死锁!
-(void)gcdDemo6{
//队里
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
//任务
void (^task)()=^{
for (int i = 0; i < 10; i++) {
NSLog(@"%d %@",i ,[NSThread currentThread]);
if (i==5) {
//1.用户登录
dispatch_sync(q, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"用户登录 %@",[NSThread currentThread]);
}
});
}
}
//2.支付
dispatch_async(q, ^{
NSLog(@"支付 %@",[NSThread currentThread]);
});
//3.下载
dispatch_async(q, ^{
NSLog(@"下载 %@",[NSThread currentThread]);
});
};
dispatch_async(q, task);
// NSLog(@"come here");
}
//MARK - 同步任务作用!
/**
在开发中,通常会将耗时操作放后台执行,有的时候,有些任务彼此有"依赖"关系!
例子: 登录,支付,下载
利用同步任务,能够做到任务依赖关系,前一个任务是同步任务,哥么不执行完,队列就不会调度后面的任务
*/
-(void)gcdDemo5{
dispatch_queue_t loginQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
//1.用户登录
dispatch_sync(loginQueue, ^{
NSLog(@"用户登录 %@",[NSThread currentThread]);
});
//2.支付
dispatch_async(loginQueue, ^{
NSLog(@"支付 %@",[NSThread currentThread]);
});
//3.下载
dispatch_async(loginQueue, ^{
NSLog(@"下载 %@",[NSThread currentThread]);
});
for (int i = 0; i< 10; i++) {
NSLog(@"......%@",[NSThread currentThread]);
}
}
//MARK : 并发队列,同步执行 和 串行队列,同步执行 效果一样!
-(void)gcdDemo4{
// 会开线程吗? 顺序执行? come here?
// 不会 顺序 最后
//1.队列 - 并发 DISPATCH_QUEUE_CONCURRENT
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
//2.同步执行任务
for (int i = 0; i < 10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
//哥么在主线程!
NSLog(@"come here");
}
//MARK : 并发队列,异步执行
-(void)gcdDemo3{
//1.队列 - 并发 DISPATCH_QUEUE_CONCURRENT
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
//2.异步执行任务
for (int i = 0; i < 10; i++) {
dispatch_async(q, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
//哥么在主线程!
NSLog(@"come here");
}
//MARK: 串行队列,异步任务
-(void)gcdDemo2{
/**
会开几条线程?会顺序执行吗?
*/
//1.队列 - 串行
dispatch_queue_t q = dispatch_queue_create("test", NULL);
//2.异步执行任务
for (int i = 0; i < 10; i++) {
NSLog(@"%d------------",i);
dispatch_async(q, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
//哥么在主线程!
NSLog(@"come here");
}
//MARK:串行队列,同步任务
/**
* 不会开启线程,会顺序执行
*/
-(void)gcdDemo1{
//1.队列 - 串行
/**
1.队列名称:
2.队列的属性: DISPATCH_QUEUE_SERIAL 标示串行!
*/
dispatch_queue_t q = dispatch_queue_create("test", NULL);
//2.同步执行任务
for (int i = 0; i < 10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
}
主队列
重点:主队列,以FIFO调度任务,如果主线程上有任务在执行,主队列就不会调度任务,如下代码能体现
- 主要是负责在主线程上执行任务
- (void)gcdDemo1 {
//主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
//1.队列 --> 已启动主线程,就可以获取主队列
dispatch_queue_t q = dispatch_get_main_queue();
//2.异步任务
dispatch_async(q, ^{
NSLog(@"111==%@",[NSThread currentThread]);
});
NSLog(@"come here");
NSLog(@"come here1");
NSLog(@"come here2");
NSLog(@"come here3");
dispatch_async(q, ^{
NSLog(@"222==%@",[NSThread currentThread]);
});
[self test];
/*
这么理解哈,整个gcdDemo1e也相当于任务在主队列里执行
test 方法就一个耗时操作
打印顺序是
come here
come here1
come here2
come here3
test的for循环打印
3333 (3333是touch方法调用gcdDemo1e后的一个打印语句)
111==
222==
如果主线程上有任务在执行,主队列就不会调度任务
所以在主队列里异步执行任务,就要等主队列的其他任务都执行完了,在执行block的任务
*/
}
- (void)gcdDemo2 {
//主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
NSLog(@"这里!!");
//1.队列 --> 已启动主线程,就可以获取主队列
dispatch_queue_t q = dispatch_get_main_queue();
//2.同步任务 ==> 死锁
dispatch_sync(q, ^{
NSLog(@"能来吗? ");
});
NSLog(@"come here");
/*
为什么是死锁呢
跟gcdDemo1的解释一样
整个gcdDemo2 包括gcdDemo2后面的都相当于一个任务在主线程里执行,
所以必须要等这个任务都执行完了再去执行dispatch_sync的任务
但是dispatch_sync又是阻塞当前线程的,也就是说dispatch_sync下面的代码要等到dispatch_sync里面的任务执行完再去执行,这不就相互等待,谁也等不到谁,所以就死锁了
*/
}
-
GCD死锁
1.死锁1
NSLog(@"111111");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2222222");
});
NSLog(@"3333333");
出现的打印现象:只打印了“111111”,并且主线程已卡死,点击啊什么的都没有效果了,这个就是传说中死锁,但是在xcode8之后死锁直接报错,如下所示
解释:同步任务会阻塞当前线程,然后把block中的任务,只有等到block中的任务完成后才会让当前线程继续往下走。这段代码的步骤:打印完第一句后,dispatch_sync 立即阻塞当前的主线程,这里关于死锁的描述有些简书模糊,“然后把 Block 中的任务放到 main_queue 中,可是 main_queue 中的任务会被取出来放到主线程中执行,但主线程这个时候已经被阻塞了,所以 Block 中的任务就不能完成”,这里“main_queue 中的任务会被取出来放到主线程中执行”描述不准确,应该是main_queue中已经有任务在执行了,而这个任务就包含了同步任务,而同步任务中的block却被放到了列队底部,由于同步任务需要阻塞当前线程,完成block才能继续执行,而队列又无法弹出该block来执行,因为这时候在队列顶部的是包含了block的任务,形成了循环依赖
2.死锁2
dispatch_queue_t cusQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"111111");
dispatch_async(cusQueue, ^{
NSLog(@"2222222");
dispatch_sync(cusQueue, ^{
NSLog(@"3333333");
});
NSLog(@"44444444");
});
NSLog(@"5555555");
出现的打印现象:
解释:
1.创建的cusQueue是serial,这个是串行队列
2.打印出1111
3.dispatch_async是异步执行,所以当前线程不会阻塞,于是有了两条线程这是并行的,那么22222和5555打印的先后是不确定的
4.注意,高潮来了。现在的情况和上一个例子一样了。dispatch_sync同步执行,于是它所在的线程会被阻塞,一直等到 sync 里的任务执行完才会继续往下。于是 sync 就高兴的把自己 Block 中的任务放到 queue 中,可谁想 queue 是一个串行队列,一次执行一个任务,所以 sync 的 Block 必须等到前一个任务执行完毕,可万万没想到的是 queue 正在执行的任务就是被 sync 阻塞了的那个。于是又发生了死锁。所以 sync 所在的线程被卡死了。剩下的两句代码自然不会打印。
3.之前误以为是死锁
dispatch_queue_t myQueue =dispatch_queue_create("myQueue", NULL);
NSLog(@"111111 :%d",[NSThread isMainThread]);
dispatch_sync(myQueue, ^{
NSLog(@"3333333 :%d",[NSThread isMainThread]);
// dispatch_sync(myQueue, ^{
// NSLog(@"44444 :%d",[NSThread //isMainThread]);
// });
});
NSLog(@"5555555 :%d",[NSThread isMainThread]);
之前认为,同步任务 + 串行队列 就是死锁,因为之前认为同步任务会阻塞了当前线程,而串行队列的任务被取出来会在当前线程执行,所以就会被死锁,这个例子根第一个死锁很相似,区别就是第一个例子是主队列,而这个例子是串行队列。那么现在开始解释这个为什么不是死锁
1.第一句代码是创建一个串行队列
2.第二句代码的1111能打印出来这个毋庸置疑
3.重点来了,第三句代码,同步任务会阻塞当前线程,这个也是肯定的,把任务放入这个自己创建的串行队列,并且这个队列之前是没有任务的,所以代码会执行完这个block里的代码再返回继续走之后的代码,
4.所以顺序是 111 333 555,并不会死锁
5.而第一例子之所以会造成死锁,那是因为把任务放入的是主队了,而执行sync那句代码也是在主队列中,执行sync时线程已经阻塞,再把block的任务取出来是必须要等sync返回才能执行,因为sync是比block先入队列,出队列是先进的先出,所以相互等待就是死锁
6.如果这个例子,把注释掉的代码打开,那么也是死锁,因为第二个sync是串行队列的第一个任务,而block是串行队列的第二个任务,于是又是相互等待造成死锁
4.死锁总结
死锁的原因不是线程阻塞,而是队列阻塞
如果dispatch_sync()的目标queue为当前queue,会发生死锁(并行queue并不会)。使用dispatch_sync()会遇到跟我们在pthread中使用mutex锁一样的死锁问题
-
GCD常用方法
dispatch_after
NSLog(@"111111"); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"22222"); }); dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 3); dispatch_after(delayTime, dispatch_get_main_queue(), ^{ NSLog(@"33333"); });
dispatch_after只是延时提交block,并不是延时后立即执行的,dispatch_after不是很精确
dispatch_apply
dispatch_queue_t cusQueue = dispatch_queue_create("cus", DISPATCH_QUEUE_CONCURRENT);
//第一个参数,3--block执行的次数
//第二个参数,applyQueue--block任务提交到的队列
//第三个参数,block--需要重复执行的任务
dispatch_apply(3, cusQueue, ^(size_t index) {
NSLog(@"current index %@",@(index));
sleep(1);
});
NSLog(@"2222");
dispatch_apply:把一项任务放入队列中多次执行,串行队列和并行队列都行,它是同步执行的函数,不会立刻返回,要等待block中的任务全部执行完才返回
dispatch_apply的正确使用方法:为了不阻塞主线程,一般把dispatch_apply放入异步队列中调用,然后执行完后通知主线程
dispatch_once
保证app在运行期间,block中的代码只执行一次,个人用的最多的就是单例的使用中
dispatch group
有时候我们会有这种需求,在刚进去一个页面需要发送两个请求,并且某种特定操作必须在两个请求都结束(成功或失败)的时候才会执行,最low的办法第二个请求嵌套在第一个请求结果后在发送,在第二个请求结束后再执行操作。还有就是只使用一个Serial Dispatch Queue,把想要执行的操作全部追加到这个Serial Dispatch Queue中并在最后追加某种特定操作,颇为复杂操作。但是呢,我们这里介绍更高级的办法使用dispatch group
dispatch_queue_t cusConQueue = dispatch_queue_create("cusConQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t cusGroup = dispatch_group_create();
dispatch_group_async(cusGroup, cusConQueue, ^{
NSLog(@"执行任务1");
});
dispatch_group_async(cusGroup, cusConQueue, ^{
NSLog(@"执行任务2");
});
dispatch_group_async(cusGroup, cusConQueue, ^{
NSLog(@"执行任务3");
});
dispatch_group_notify(cusGroup, cusConQueue, ^{
NSLog(@"执行所有任务后想要的操作");
});
NSLog(@"44444----");
上图中的 1 2 3 4执行的顺序都不一定,因为他们都是异步,1 2 3任务都执行完成后才会执行 notif里的任务
上面的dispatch_group_notify还可以换成dispatch_group_wait,代码如下
dispatch_queue_t cusConQueue = dispatch_queue_create("cusConQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t cusGroup = dispatch_group_create();
dispatch_group_async(cusGroup, cusConQueue, ^{
NSLog(@"执行任务1");
});
dispatch_group_async(cusGroup, cusConQueue, ^{
NSLog(@"执行任务2");
});
dispatch_group_async(cusGroup, cusConQueue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"执行任务3");
});
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1);
long result=dispatch_group_wait(cusGroup, DISPATCH_TIME_FOREVER);
if (result==0) {
NSLog(@"任务执行完成");
}
else{
NSLog(@"任务执行还在继续");
}
NSLog(@"44444----");
dispatch_group_wait第二个参数指定为等待的时间(超时),属于dispatch_time_t类型,在这里使用DISPATCH_TIME_FOREVER,意味着永久等待。如果dispatch group的处理尚未结束,就会一直等待,它会阻塞线程,所以不会放在主线程里执行,所以如果group的任务没有处理完,代码是不会执行dispatch_group_wait之后的代码,所以这里的打印 1 2 3 是无序,但是4一定是在最后
但是呢上面这种dispatch_group的排列执行方式,是不会考虑block块内部的异步请求情况的,它只能保证把block内的非异步直观代码执行完,所以如果ABC三个任务中如果有执行异步的请求,那么在dispatch_group_notify最终任务执行中,那个异步请求不一定毁掉结束,所以又给应该介绍新的api
dispatch_group_enter/dispatch_group_leave
调用dispatch_group_enter这个方法标志着一个代码块被加入了group,和dispatch_group_async功能类似;
需要和dispatch_group_enter()、dispatch_group_leave()成对出现;编译器会强制识别当出现dispatch_group_leave全部结束才执行dispatch_group_notify
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(5);
NSLog(@"任务一完成");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(8);
NSLog(@"任务二完成");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务完成");
});
dispatch_barrier_async
这个函数可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-1");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-2");
});
dispatch_barrier_async(concurrentQueue, ^(){
NSLog(@"dispatch-barrier");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-3");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-4");
});
如上都是GCD一些常用的方法,还有一些不常用也没去做记录了