一、进程
1.1 什么是进程
- 进程是指在系统中正在运行的一个应用程序
- 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内
- 比如同时打开Xcode和QQ,就会开启两个进程
- 通过活动“活动监视器”可以查看MAC系统中所有的进程
二、线程
2.1 什么是线程
- 一个进程要想执行任务,必须得有线程(每一个进程至少要有一个线程)
- 线程是进程的基本单元,一个程序所有的任务都做线程中执行
- 使用迅雷下载电影、使用网易云听歌,都需要在线程中执行
2.2 线程的串行
- 一个线程的任务是串行的(同一时间内,一个线程只能执行一个任务)
三、多线程
3.1 what
- 一个进程中可以开启多条线程,每条线程可以同时(并行)执行不同的任务
- 多线程可以提高程序的执行效率
3.2 多线程的原理
- 同一时间,cpu只能处理一条线程
- 多线程并发执行,其实是cpu快速的在多条线程之间调度(切换),如果cpu调度线程的时间足够快,就造成了多线程并发执行的假象。
3.3 多线程的优点
- 能适当提高程序的执行效率
- 能适当提高资源的利用率(cpu 、内存)
3.4 多线程的缺点
- 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
- 线程越多,cpu在调度线程上的开销就越大
- 使程序的设计更加复杂:比如多线程之间的通信,多线程之间的数据共享
四、多线程在iOS中的应用
4.1 什么是主线程
- 一个ios程序运行后,默认会开启一条线程,称为“主线程”或“UI线程”
4.2 主线程的主要作用
- 显示\刷新UI界面
- 处理UI事件(比如点击事件、滚动事件、拖拽事件等)
4.3 主线程的使用注意
- 别将比较耗时的操作放到主线程中,耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的体验。
4.4 实现方案
五、NSThread
5.1 what
一个NSThread对象就代表一个线程
- 创建、启动线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"哈哈"]; thread.name = @"线程A";//自己来区别的 // 开启线程 [thread start];
- 获得当前线程
NSThread *current = [NSThread currentThread]; NSLog(@"btnClick---%@", current);
- 其他创建线程的方式
//创建完线程直接(自动)启动 [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"我是参数"]; // 在后台线程中执行 === 在子线程中执行 [self performSelectorInBackground:@selector(run:) withObject:@"abc参数"]; 以上2种方式的优点:快捷方便 缺点:无法对线程进行更详细的设置
六、线程的状态
线程一共有5种状态,如图:
6.1新建状态和就绪状态
现在以NSThread为例,黄色为例。
[tehread start]之前是新建状态,start之后进入了就绪状态(cpu在调度其他进程的时候也是处于调度状态),这个时候就进入了可调度线程池,放进可调度的线程池的线程是可以来回进行调度的。
6.2运行状态
线程经过cpu调度之后就进入了运行状态。
6.3阻塞状态
调用了sleep方法/等待同步锁的时候,就进入了阻塞状态。
6.4死亡状态
线程任务执行完毕,或者异常/强制退出等,就进入了死亡状态。
七、多线程的安全隐患
7.1 why
-
资源共享
一个资源可能被多个资源共享(多个线程可能访问同一个资源),比如多个线程访问同一个变量、同一个对象、同一个文件等,当多个线程访问同一个资源时,很容易引发数据安全和数据错乱问题
如下图分析:
7.2解决方法
-
互斥锁
@synchronized(锁对象){
}
注意:锁定一份代码只用一把锁,用多把锁是无效的
*互斥锁的优点、缺点
优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的cpu资源
线程同步
多条线程按顺序的执行任务
互斥锁就是使用了线程同步技术-
原子性和非原子性
oc在定义属性时有2种选择- atomic: 原子属性,为setter方法加锁(默认就是atomic),线程安全,需要消耗大量的资源
- nonatmic: 非原子属性,不会为setter方法加锁,非线程安全,适合内存小的移动设备
ios开发建议:所有的属性都声明为nonatmic,尽量避免多线程抢夺同一块资源,尽量将加锁、抢夺资源的业务逻辑交给服务器端处理,减小移动端的压力。
八、线程之间的通信
8.1 what
在一个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信
8.2 体现
- 一个线程传递数据给另外一个线程
- 在一个线程中执行完一个特定的任务后,转到另一个线程继续执行任务。
import "ViewController.h"
@interface HMViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation HMViewController
-(void)viewDidLoad{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
-(void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event
{
// 在子线程中调用download方法下载图片
[self performSelectorInBackground:@selector(download) withObject:nil];
}
/
下载图片 : 子线程
*/
-(void)download
{
// 1.根据URL下载图片
NSURL *url = [NSURL URLWithString:@"http://news.baidu.com/z/resource/r/image/2014-06-22/2a1009253cf9fc7c97893a4f0fe3a7b1.jpg"];
NSLog(@"-------begin");
NSData data = [NSData dataWithContentsOfURL:url]; // 这行会比较耗时
NSLog(@"-------end");
UIImage image = [UIImage imageWithData:data];
// 2.回到主线程显示图片
// [self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO];
// setImage: 1s
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
// [self performSelectorOnMainThread:@selector(settingImage:) withObject:image waitUntilDone:NO];
}
/
设置(显示)图片: 主线程
*/
//- (void)settingImage:(UIImage *)image
//{
// self.imageView.image = image
//}
@end
九、GCD
9.1 what
- 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器”。
- 纯c语言,提供了非常多、强大的函数
9.2 优势
- GCD是苹果公司为多核的并行运算提出的解决法案
- GCD会自动利用更多的CPU内核,会自动管理线程的生命周期(创建线程、调度线程、销毁线程)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
9.3 队列和任务
- 任务:执行什么操作
- 队列:用来存放任务
- 并发队列:可以让多个任务同时执行,只有在异步函数下才有效
- 串行队列:让任务一个接着一个的执行
- 同步:在当前线程中执行,不具备开启线程的能力
- 异步:在另一条线程中执行,具备开启线程的能力
GCD会自动将队列中的任务取出,放到对应的线程中执行,任务的取出遵循队列的FIFO原则:先进先出,后进后出
9.4 并发对列
GCD默认已经提供了全局的并发对列,供整个应用使用,不需要手动创建
// 1.获得全局的并发队列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
9.5串行队列
GCD中获得串行有2中途径
// 1.创建串行队列 dispatch_queue_t queue = dispatch_queue_create("com.baidu.queue", NULL);
//2.使用主队列(跟主线程相关联的队列) dispatch_queue_t queue = dispatch_get_main_queue();
各队列的执行效果
上代码
#import "ViewController.h"
@interface HMViewController ()
@end
@implementation HMViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelectorInBackground:@selector(test) withObject:nil];
// [self syncMainQueue];
}
- (void)test
{
NSLog(@"test --- %@", [NSThread currentThread]);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"任务 --- %@", [NSThread currentThread]);
});
}
/**
* 使用dispatch_async异步函数, 在主线程中往主队列中添加任务
*/
- (void)asyncMainQueue
{
// 1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2.添加任务到队列中 执行
dispatch_async(queue, ^{
NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
});
}
/**
* 使用dispatch_sync同步函数, 在主线程中往主队列中添加任务 : 任务无法往下执行
*/
- (void)syncMainQueue
{
// 1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2.添加任务到队列中 执行
dispatch_sync(queue, ^{
NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
});
// dispatch_sync(queue, ^{
// NSLog(@"----下载图片2-----%@", [NSThread currentThread]);
// });
// dispatch_sync(queue, ^{
// NSLog(@"----下载图片3-----%@", [NSThread currentThread]);
// });
// 不会开启新的线程, 所有任务在主线程中执行
}
// 凡是函数名种带有create\copy\new\retain等字眼, 都需要在不需要使用这个数据的时候进行release
// GCD的数据类型在ARC环境下不需要再做release
// CF(Core Foundation)的数据类型在ARC环境下还是需要再做release
/**
* 用dispatch_sync同步函数往串行列中添加任务
*/
- (void)syncSerialQueue
{
// 1.创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", NULL);
// 2.添加任务到队列中 执行
dispatch_sync(queue, ^{
NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"----下载图片2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"----下载图片3-----%@", [NSThread currentThread]);
});
// 3.释放资源
// dispatch_release(queue); // MRC(非ARC)
// 总结: 不会开启新的线程
}
/**
* 用dispatch_sync同步函数往并发队列中添加任务
*/
- (void)syncGlobalQueue
{
// 1.获得全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2.添加任务到队列中 执行
dispatch_sync(queue, ^{
NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"----下载图片2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"----下载图片3-----%@", [NSThread currentThread]);
});
// 总结: 不会开启新的线程, 并发队列失去了并发的功能
}
/**
* 用dispatch_async异步函数往串行队列中添加任务
*/
- (void)asyncSerialQueue
{
// 1.创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.baidu.queue", NULL);
// 2.添加任务到队列中 执行
dispatch_async(queue, ^{
NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----下载图片2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----下载图片3-----%@", [NSThread currentThread]);
});
// 总结: 只开1个线程执行任务
}
/**
* 用dispatch_async异步函数往并发队列中添加任务
*/
- (void)asyncGlobalQueue
{
// 1.获得全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2.添加任务到队列中 执行
dispatch_async(queue, ^{
NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----下载图片2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----下载图片3-----%@", [NSThread currentThread]);
});
// 总结: 同时开启了3个线程
}
@end
9.6 线程间的通信
从子线程回到主线程
上代码
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"--download--%@", [NSThread currentThread]);
// 下载图片
NSURL *url = [NSURL URLWithString:@"http://news.baidu.com/z/resource/r/image/2014-06-22/2a1009253cf9fc7c97893a4f0fe3a7b1.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url]; // 这行会比较耗时
UIImage *image = [UIImage imageWithData:data];
// 回到主线程显示图片
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"--imageView--%@", [NSThread currentThread]);
self.imageView.image = image;
});
});
}
9.7 延时执行
2种方法
- (void)delay
{
// NSLog(@"----touchesBegan----%@", [NSThread currentThread]);
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];//第一种
// });
// 1.全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2.计算任务执行的时间
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
// 3.会在when这个时间点, 执行queue中的任务。第二种
dispatch_after(when, queue, ^{
NSLog(@"----run----%@", [NSThread currentThread]);
});
}
//- (void)run
//{
// NSLog(@"----run----%@", [NSThread currentThread]);
//}
9.8一次性代码
保证某段代码在程序运行的过程中只被执行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"-------touchesBegan");
});
}
9.9 队列组
首先有一个需求,分别异步执行2个耗时的操作,等2个异步操作都执行完毕后,再回到主线程执行操作。
dispatch_group_t group = dispatch_group_create();
上代码
/**
1.下载图片1和图片2
2.将图片1和图片2合并成一张图片后显示到imageView上
思考:
* 下载图片 : 子线程
* 等2张图片都下载完毕后, 才回到主线程
*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 创建一个组
dispatch_group_t group = dispatch_group_create();
// 开启一个任务下载图片1
__block UIImage *image1 = nil;
dispatch_group_async(group, global_queue, ^{
image1 = [self imageWithURL:@"http://news.baidu.com/z/resource/r/image/2014-06-22/2a1009253cf9fc7c97893a4f0fe3a7b1.jpg"];
});
// 开启一个任务下载图片2
__block UIImage *image2 = nil;
dispatch_group_async(group, global_queue, ^{
image2 = [self imageWithURL:@"http://news.baidu.com/z/resource/r/image/2014-06-22/b2a9cfc88b7a56cfa59b8d09208fa1fb.jpg"];
});
// 同时执行下载图片1\下载图片2操作
// 等group中的所有任务都执行完毕, 再回到主线程执行其他操作
dispatch_group_notify(group, main_queue, ^{
self.imageView1.image = image1;
self.imageView2.image = image2;
// 合并
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);
[image1 drawInRect:CGRectMake(0, 0, 100, 100)];
[image2 drawInRect:CGRectMake(100, 0, 100, 100)];
self.bigImageView.image = UIGraphicsGetImageFromCurrentImageContext();
// 关闭上下文
UIGraphicsEndImageContext();
});
}
- (UIImage *)imageWithURL:(NSString *)urlStr
{
NSURL *url = [NSURL URLWithString:urlStr];
NSData *data = [NSData dataWithContentsOfURL:url]; // 这行会比较耗时
return [UIImage imageWithData:data];
}
十、NSOperation
10.1 what
作用:配合使用NSOperation和NSOperationQueue也能实现多线程编程
实现步骤:
先将需要执行的操作封装到一个NSOperation对象中
然后将NSOperation对象添加到NSOperationQueue中
系统会自动将NSOperation中封装的操作放到一条新线程中执行
10.2 how
NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类
使用NSOperationa子类有3种
- NSInvocationOperation
- NSBlockOperation
- 自定义子类继承NSOperation,实现内部相应的方法
10.2.1 NSInvocationOperation
- 创建SInvocationOperation对象
- 调用start方法开始执行操作
注意:
默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作,只有将NSOperation放到NSOperationQueue中,才会异步执行操作。
-(void)invocationOperation
{
// 1.创建操作对象, 封装要执行的任务
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
// 2.执行操作(默认情况下, 如果操作没有放到队列queue中, 都是同步执行)
[operation start];
}
-(void)download
{
for (int i = 0; i<10; i++) {
NSLog(@"------download---%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.1];
}
}
10.2.2 NSBlockOperation
如下代码会开启3个线程,线程数取决于任务的个数
-(void)blockOperation
{
// 1.封装操作
// NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
// NSLog(@"NSBlockOperation------下载图片1---%@", [NSThread currentThread]);
// }];
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
[operation addExecutionBlock:^{
NSLog(@"NSBlockOperation------下载图片1---%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"NSBlockOperation------下载图片2---%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"NSBlockOperation------下载图片3---%@", [NSThread currentThread]);
}];
// 2.执行操作
[operation start];
}
添加到NSOperationQueue的操作
- (void)operationQueue
{
// 1.封装操作
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
// operation1.queuePriority = NSOperationQueuePriorityHigh
NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i<10; i++) {
NSLog(@"NSBlockOperation------下载图片---%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.1];
}
}];
// [operation3 addExecutionBlock:^{
// for (int i = 0; i<10; i++) {
// NSLog(@"NSBlockOperation------下载图片2---%@", [NSThread currentThread]);
// [NSThread sleepForTimeInterval:0.1];
// }
// }];
// 2.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//设置并发数
queue.maxConcurrentOperationCount = 2; // 2 ~ 3为宜
// 设置依赖
[operation2 addDependency:operation3];
[operation3 addDependency:operation1];
// 3.添加操作到队列中(自动执行操作, 自动开启线程)
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
// [queue setSuspended:YES];
}
10.3 对列的暂停、取消和恢复
[queue setSuspended:YES];//yes代表暂停,no代表恢复
[queue cancel]// 取消
10.4 操作依赖(代码在上边)
10.5 NSOperation下载图片的思路
github上有一个比较好的下载图片框架,sdwebimage,可以去研究一下。