基本概念
- 进程:系统中正在运行的一个应用程序。
- 线程:进程想要执行任务,必须要有线程(每1个进程至少要有个1条线程),线程是进程的基本单元,一个进程的所有任务都是在线程中执行。
- 线程的串行:1个线程中任务的执行时串行的,若1个线程中执行多个任务,只能一个个的按顺序执行。同一时间内,1个线程只能执行1个任务。
- 多线程
- 一个进程中开启多条线程,每条线程可以并行(同时)执行不同的任务
- 什么叫线程的并行
- 并行即同时执行。若同时开启3条线程分别下载3个文件(文件A,文件B,文件C)
- 多线程并发执行的原理
- 同一时间CPU只能处理1条线程。多线程并发执行,只是CPU在多条线程之间进行切换,如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。
5.多线程的优缺点
- 优点
- 能适当提高程序的执行效率
- 能适当提高资源利用率(CPU,内存利用率)
- 缺点
- 开启线程需要占用一定的内存空间,若开启大量的线程,则会占用大量的内存空间,降低程序的性能
- 线程越多,CPU在调度线程上的开销就越大
- 程序设计更加复杂(线程之间的通信,多线程的数据共享)
6.iOS中多线程的实现方案
- pthread
- NSThread
- GCD
- NSOperation
NSThread
-
创建方式
- [alloc init];
- 需要手动开启线程;
- 可以拿到线程对象
- [alloc init];
分离一条子线程;
- 不可拿到线程对象;-
后台创建一条线程;
- 不可拿到线程对象;
线程状态
新建
就绪
运行
阻塞
死亡(线程死亡之后,不能再重新启用)
-
线程安全
- 多个线程访问同一块区域
- 增加互斥锁
- 相关代码:@synchoronized(self){}(一般都是使用self)
- 这叫线程同步
原子属性和非原子属性
用@porperty生命一个属性的时候,内部会进行三步操作
1.生成一个有下划线的成员变量
2.生成一个setter方法
3.生成一个getter方法原子属性 : atomic(为setter方法加锁,默认就是atomic)
非原子属性:nonatomic(不会为setter方法加锁)
-
为什么开发中经常使用nonatomic而不是atomic
-
atomic
代表线程安全,需要消耗大量资源 -
nonatomic
代表非线程安全,适合内存小的移动设备 - 在开发中尽量使用
nonatomic
- 尽量避免多线程访问同一资源
- 尽量将加锁、资源抢夺的业务逻辑交给服务器,从而减少客户端的压力
-
线程间通信
把程序中的耗时操作尽量放到子线程当中进行
必须要回到主线程中进行UI刷新
GCD
-
两个核心概念
- 任务 : 执行什么操作
- 队列 : 用来储存任务的
同步函数/异步函数
同步函数:只能在当前线程中进行任务,不具备开启子线程的能力,立刻马上执行任务
异步函数:可以在新线程中执行任务,具备开启子线程的能力
并发队列/串行队列
并发队列 : 多个任务可以同时执行,前提是在异步函数的情况下
可以创建并发队列,也可以获取并发队列。GCD里本身存在一个并发队列
串行队列 : 任务不能同时执行,需一个接一个的执行
主队列 : 放在主队列里面的任务只能在主线程中执行,并且也是一个接一个的执行任务
GCD的使用
-
异步函数 + 并发队列 : 开启子线程,并发执行任务
//1.创建队列 dispatch_queue_t queue = dispatch_queue_creat("one",DISPATHC_QUEUE_CONCURRENT); //2.封装任务,将任务添加到队列中 dispathc_async(queue,^{ NSLog(@"xxxx"); });
异步函数 + 串行队列 : 开启一条线程,串行执行任务
//1.创建队列
dispatch_queue_t queue = dispatch_queue_creat("two",DISPATCH_QUEUE_SERIAL);
dispatch_async(queue,^{
NSLog(@"xxxx");
});
- 同步函数 + 并发队列 : 不开线程,串行执行任务
//1.创建队列
dispatch_queue_t queue = dispatch_queue_creat("three",DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue,^{
NSLog(@"xxx");
});
- 同步函数 + 串行队列 : 不开线程,串行执行任务
//1.创建队列
dispatch_queue_t queue = dispatch_queue_creat("four",DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue,^{
NSLog(@"xxx");
});
- 异步函数 + 主队列 : 不开线程,在主线程中串行执行任务
//1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
//
dispatch_async(queue,^{
NSLog(@"xxx");
});
-
同步函数 + 主队列 : 造成死锁状况
//获得主队列 dispatch_queue_t queue = dispatch_get_main_queue();
//
dispatch_sync("six",^{
NSLog(@"xxx");
});
- GCD中的线程通信
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSURL * url = [NSURL URLWithString:@"http://static.jstv.com/img/2016/5/18/20165181463528366178_0.jpg"];
NSData * imagedate = [NSData dataWithContentsOfURL:url];
UIImage * image = [UIImage imageWithData:imagedate];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
}
- GCD的常用函数
- 延迟
![GCD中的延迟](http://upload-images.jianshu.io/upload_images/1404354-5a589a7e984c4150.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![调用延迟方法](http://upload-images.jianshu.io/upload_images/1404354-b691033dde114ed0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![NSTimer](http://upload-images.jianshu.io/upload_images/1404354-63a320ee97f5eea8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 一次代码
![一次代码](http://upload-images.jianshu.io/upload_images/1404354-18b974230d99a0ab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 栅栏函数
- 用于异步函数
- 用于控制多线程执行任务顺序
- 在使用栅栏函数的时候,*不能使用全局并发队列,只能进行手动创建*。
- 栅栏函数之前的线程执行顺序,栅栏函数是没有办法进行控制的
![栅栏函数](http://upload-images.jianshu.io/upload_images/1404354-7b9aa4054951553b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 快速迭代
- 开启多个线程,完成快速迭代操作
- 类似于for循环
- GCD里面的快速迭代是并发队列
- for循环里面是串行队列
小案例:图片的移动
思路:(使用了GCD里面的快速迭代)
1.获得最初文件夹的路径
2.获得目的文件夹的为路径
3.移动文件需要全路径,需要对最初文件夹下的文件进行路径拼接
4.文件名不变,所以目的文件夹的文件路径也需要进行拼接
5.然后用文件管理者进行文件移动
- 案例代码
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t index) {
- 第一个参数:遍历的次数
- 第二个参数: 队列,必须使用并发队列
3.第三个参数:设置索引
};
![GCD快速迭代 图片移动](http://upload-images.jianshu.io/upload_images/1404354-b1633fd217ea64d3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 队列组
- 用来控制队列任务的完成情况
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//将队列添加到队列组中,执行任务(下载图1)
dispatch_group_async(group, queue, ^{
//确定图片地址
NSURL * url = [NSURL URLWithString:@"http://img.2258.com/d/file/yule/mingxing/neidi/2016-04-20/6b6d95c044b5282cf5b8c78f73c23c4c.jpg"];
//根据图片地址下载二进制数据
NSData * imageData = [NSData dataWithContentsOfURL:url];
//转换二进制数据
self.image1 = [UIImage imageWithData:imageData];
});
//将队列添加到队列组中,执行任务(下载图2)
dispatch_group_async(group, queue, ^{
//确定图片地址
NSURL * url = [NSURL URLWithString:@"http://pic.yesky.com/uploadImages/2016/126/00/7HLRG65LQ5FJ.jpg"];
//根据图片地址下载二进制数据
NSData * imageData = [NSData dataWithContentsOfURL:url];
//转换二进制数据
self.image2 = [UIImage imageWithData:imageData];
});
//当队列组中的任务完成之后会进入这个方法
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//开启图片上下文
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
//绘制图片到上下文中
[self.image1 drawInRect:CGRectMake(0, 0, 100, 200)];
[self.image2 drawInRect:CGRectMake(100, 0, 100, 200)];
//获得新图片
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
//关闭图片上下文
UIGraphicsEndImageContext();
//回到主线程,刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
// 设置图片
self.image.image = newImage;
});
});
- 这个方法内部并不是阻塞,内部本身是异步的
![方法内部](http://upload-images.jianshu.io/upload_images/1404354-100b4d4670a1fc69.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 这个方法是阻塞的,会等之前的任务执行完成之后才能执行
![等待](http://upload-images.jianshu.io/upload_images/1404354-c561530c74eb514e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 关于队列组的另一种写法
//获得全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//创建队列组
dispatch_group_t group = dispatch_group_create();
//在该方法后面的异步任务会被纳入监听范围,进入队列组
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
//任务执行完成之后离开队列组
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
- 两个异步函数的方法的区别
1. 一个用block块封装任务
2.一个用函数来进行任务的封装
![两个方法的区别](http://upload-images.jianshu.io/upload_images/1404354-dcf9193766c33fbf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 全局并发队列和手动创建的并发队列的区别
- 全局并发队列在GCD中本身就存在的,而手动创建的并发队列是重新创建的
- 在使用栅栏函数的时候,必须要使用手动创建的并发队列,这样才能有效果
- 在iOS6以前,GCD中只要带有了Creat和retain函数,在最后都要进行一次release操作。但是现在GCD已经被纳入ARC管理范围,已经不需要我们再进行手动release操作了。