1、进程:一个运行的程序就是一个进程。
一个进程至少包含一个线程。系统的每一个进程都有自己独立的虚拟内存空间。
同一个进程中的多个线程则共用进程的内存空间。
2、线程:就是要执行的任务流或者会是程序的执行流。
3、为什么要使用多线程?
当一个进程有消耗性能或者时间的操作比如:下载、播放功能。可能需要创建一个线程,这个时候应该新建线程将这些程序在新的线程中实现。新的来单独处理这些耗时的操作,待处理完之后再来跟新UI界面。但是如果不让耗时操作在其他线程里执行可能会“阻塞”主线程的执行,影响用户体验。
多线程的意义是通过并发完成多项任务而提高资源的使用效率来提高整个系统的性能!
4、主线程:
iOS程序启动创建一个进程的同时会运行一个线程该线程被称为主线程。所有界面的显示必须在主线程中执行。主线程的堆栈大小是1M。
5、子线程:
从主线程中分离出来的线程叫做子线程或者叫后台线程。每创建一个新的线程都会消耗一定的内存空间。因此一般情况下能少用则尽量少用线程。每一个子线程开始都是512KB
注意:当多个线程对同一个资源出现争夺的时候,需要注意线程安全。
6、多线程的主要思想:
程序的整个执行可以看成是个流,在程序启动后生成一个主线程,主线程中如果有耗时操作就可以分离出来在子线程中实现。像下图中右侧好比是一个TableView,在里面要显示图片和新闻标题,而图片的加载很耗性能,因此可以在主线程中开个子线程继续下载图片,这样子线程也不会影响到主线程的操作,这样就有利于系统资源的充分利用,而提高性能。
在多核处理器中,充分发挥多线程的优势并发执行任务让系统运行的速度更快
7、IOS中常用的三种多线程技术:
(1) NSThread 继承于NSObject使用父类中定义的线程方法可实现
使用简单,但是需要开发者去管理线程的生命周期、线程同步、加锁、睡眠、唤醒。线程同步对数据的加锁会有一定的系统开销
以下两种是由苹果开发的“并发”技术,致力于解决并发操作的执行,使得程序员不再去关心线程内部的原理
(2) NSOperation/NSOperationQueue
遵循面向对象思想。不需要关心线程的管理、数据同步等问题。主要让开发者关注于执行的操作上。
(3) GCD (Grand Central Dispathc)派发,是基于C语言的
专门针对多核,是替代NSThread、NSOperation的高效和强大的技术
先查看下 NSObject 类里面定义操作线程的方法:
@interface NSObject (NSThreadPerformAdditions)
// 执行方法跳转到主线程 aSlector指定一个自定义的线程方法名
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5,2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5,2_0);
// 执行方法跳转到自定义的线程中 aSlector指定一个要跳入到的线程的方法名
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg NS_AVAILABLE(10_5,2_0);
/**
模拟一个网络下载图片的延时
延时:创建一个耗时较长的操作,循环打印NSLog() 也可以使用线程休眠的方法来模拟延时:
[NSThread sleepForTimeInterval:2f];
下载:在界面中心放置一个按钮和一张UIImageView
点击按钮后显示图片
并且使用[NSThread currentThread]分别打印各个任务所在的线程
运行观察效果
*/
@interface YLViewController ()
// 定义UIImageView属性 注意是weak 弱引用
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation YLViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"DidLoad 属于主线程执行%@",[NSThread currentThread]); // 线程 num = 1
}
#pragma mark 新建个线程用来存放消耗性能的代码
- (void)NewBtnThread
{
/**
@autoreleasepool 表示自动释放池,负责其他线程上的内存管理
在使用NSThread或者NSObject类定义的线程方法时,一定要使用自动释放池。否则容易导致内存泄漏
使用时只要将涉及线程的代码放在@autoreleasepool的大括号中就可以了
*/
@autoreleasepool {
for (int i = 0; i<3 ; i++) {
NSString *str = [NSString stringWithFormat:@"%d",i];
NSLog(@"线程任务在执行 ---> %@", str);
}
NSLog(@"子线程方法执行完毕:%@",[NSThread currentThread]); // 线程 num = 2
UIImage *image = [UIImage imageNamed:@"头像1.png"];
/**
_imageView.image = image; 这一步已经能让图片显示了,但是强烈不建议在子线程中修改界面
从子线程中跳到主线程中去修改页面执行addImage:方法 withObject:是方法的参数
waitUntilDone:是否等待这行代码执行完成 一般选择YES
*/
[self performSelectorOnMainThread:@selector(addImage:) withObject:image waitUntilDone:YES];
}
}
#pragma mark 定义addImage方法来添加图片
- (void)addImage:(UIImage *)image
{
NSLog(@"在主线程中更新页面%@",[NSThread currentThread]);
_imageView.image = image;
}
#pragma mark Action 通过运行结果得知:按钮点击操作里的所有代码都在主线程中运行 返回的 num = 1
- (IBAction)btnSmallTask {
NSLog(@"线程的方法还没有执行%@",[NSThread currentThread]); // 线程 num = 1
[self performSelectorInBackground:@selector(NewBtnThread) withObject:nil];
NSLog(@"按钮的点击操作方法执行完毕%@",[NSThread currentThread]); // 线程 num = 1
}
运行结果如下:
2013-12-28 01:33:02.445 1227多线程初体验[3214:70b] DidLoad 属于主线程执行<NSThread: 0x8a6b0e0>{name = (null), num = 1}
2013-12-28 01:33:03.413 1227多线程初体验[3214:70b] 线程的方法还没有执行<NSThread: 0x8a6b0e0>{name = (null), num = 1}
2013-12-28 01:33:03.414 1227多线程初体验[3214:3807] 线程任务在执行 ---> 0
2013-12-28 01:33:03.414 1227多线程初体验[3214:70b] 按钮的点击操作方法执行完毕<NSThread: 0x8a6b0e0>{name = (null), num = 1}
2013-12-28 01:33:03.415 1227多线程初体验[3214:3807] 线程任务在执行 ---> 1
2013-12-28 01:33:03.416 1227多线程初体验[3214:3807] 线程任务在执行 ---> 2
2013-12-28 01:33:03.416 1227多线程初体验[3214:3807] 子线程方法执行完毕:<NSThread: 0xbf658c0>{name = (null), num = 2}
2013-12-28 01:33:03.419 1227多线程初体验[3214:70b] 在主线程中更新页面<NSThread: 0x8a6b0e0>{name = (null), num = 1}
观察运行结果:
程序启动后:DidLoad方法已经在主线程中执行完毕 num = 1
点击按钮后 程序首先调用 btnSmallTask 方法后开始执行直到结束都是在主线程中进行 num = 1 其中这行代码:
[self performSelectorInBackground:@selector(newBtnThread) withObject:nil];
这行代码意味着将newBtnThread方法放在了子线程中执行,那么此时程序开启了子线程后并没有停下而是继续向下执行,这意味着并发操作开始了:
在2013-12-28 01:33:03.414 这个时刻同时打印了两条信息
2013-12-28 01:33:03.414 1227多线程初体验[3214:3807] 线程任务在执行 ---> 0
2013-12-28 01:33:03.414 1227多线程初体验[3214:70b] 按钮的点击操作方法执行完毕<NSThread: 0x8a6b0e0>{name = (null), num = 1}
打印完毕后,点击按钮的方法已经执行完毕而newBtnThread的线程方法还在继续执行,执行到下面代码的时候,意味着又一个并发操作开始了
[self performSelectorOnMainThread:@selector(addImage:) withObject:image waitUntilDone:YES];
执行添加图片的addImage方法操作跳入到了主线程,这个时候子线程和主线程在2013-12-28 01:33:03.416又并发执行,打印信息如下:
2013-12-28 01:33:03.416 1227多线程初体验[3214:3807] 线程任务在执行 ---> 2
2013-12-28 01:33:03.416 1227多线程初体验[3214:3807] 子线程方法执行完毕:<NSThread: 0xbf658c0>{name = (null), num = 2}
此时子线程已经结束,主线程打印:
在主线程中更新页面<NSThread: 0x8a6b0e0>{name = (null), num = 1}
主线程添加图片