多线程概述
NSThread
NSOperationQueue
GCD
多线程管理
多线程概述
程序、进程、线程
程序:由源代码生成的可执行的应用。(例如:QQ .app)
进程:一个正在运行的程序可以看做一个进程。(例如:正在运行的QQ就是一个进程),进程拥有独立运行所需的所有资源。
线程:程序中独立运行的代码段。(例如:接收QQ消息的代码)
一个程序是由一或多个线程组成。进程只负责资源的调度和分配,线程才是程序运行的执行单元,负责代码的执行。
单线程
每个正在运行的程序(即进程),至少包含一个线程,这个线程叫做主线程
主线程在程序启动时被创建,用于执行main函数
只有一个主线程的程序,称作单线程程序
主线程负责执行程序的所有代码(UI展现以及刷新,网络请求,本地存储等)。这些
代码只能顺序执行,无法并发执行
多线程
拥有多个线程的程序,称作多线程程序
iOS允许用户自己开辟新的线程,相对于主线程来讲,这些线程,称作子线程
可以根据需求开辟若干子线程
子线程和主线程都是独立的运行单元,各自的执行互不影响,因此
能够并发执行
单线程、多线程的区别
单线程程序:只有一个线程,代码顺序执行,容易出现代码阻塞(页面假死)
多线程程序:有多个线程,线程间独立执行,能够有效地避免代码阻塞,并且提高程序的运行性能
注意:iOS中关于UI的添加和刷新必须在主线程中操作
iOS系统下的多线程
iOS多线程实现种类
NSThread
NSThread是一个轻量级的多线程,它有以下两种创建方法
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
+ (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
start 开启子线程
cancel 取消当前子线程
// 开启一个子线程,让for循环在子线程中去做
//1.手动启动
NSThread *thread = [[NSThread alloc] initWithTarget:selfselector:@selector(cycle:) object:@"线程"];
// 启动线程
[thread start];
[thread release];
// 2.自动启动
[NSThread detachNewThreadSelector:@selector(cycle:) toTarget:selfwithObject:nil];
#pragma mark 计算for循环和调用加载图片方法的方法
- (void)cycle:(NSString *)str
{
for (int i = 1; i < 1000; i++) {
NSLog(@"%d", i);
}
[self downLoadImage];
}
#pragma mark 加载图片
- (void)downLoadImage
{
// 自动释放池
@autoreleasepool {
NSLog(@"%@ %d", [NSThread currentThread], [NSThread isMainThread]);
//1.组拼Url地址
NSURL *url = [NSURLURLWithString:@"http://pic1.win4000.com/wallpaper/6/53bfb7f60d991.jpg"];
// 2.创建请求对象
NSMutableURLRequest *request = [NSMutableURLRequestrequestWithURL:url];
// 3.创建链接对象,发送请求,获取数据
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:nil error:nil];
//4.转成UIImage类型
UIImage *image = [UIImage imageWithData:data];
// 5.显示图片(更新页面需要回到主线程)
[self performSelectorOnMainThread:@selector(showImage:)withObject:image waitUntilDone:YES];
}
}
注意!
在多线程方法中需要添加自动释放池!
在应用程序打开的时候,系统会自动为主线程创建一个自动释放池
我们手动创建的子线程需要我们手动添加自动释放池
NSOperation
NSOperation类,在MVC中属于M,是用来封装单个任务相关的代码和数据的抽象类
因为它是抽象的,不能够直接使用这个类,而是使用子类(NSInvocationOperation或NSBlockOperation)来执行实际任务
NSOperation子类:
NSInvocationOperation、NSBlockOperation,只是一个操作,本身无主线程、子线程之分,可在任意线程中使用。通常与NSOperationQueue结合使用
NSInvocationOperation是NSOperation的子类:封装了执行操作的target和要执行的action
NSBlockOperation是NSOperation的子类:封装了需要执行的代码块
NSOperationQueue
将NSOperation添加到NSOperationQueue中实现多线程操作
NSOperationQueue是操作队列,它用来管理一组Operation对象的执行,会根据需要自动为Operation开辟合适数量的线程,以完成任务的并行执行
其中NSOperation可以调节它在队列中优先级
当最大并发数设置为1的时候,能实现线程同步
NSObject实现异步后台执行
NSObject中存在了一个最简单的后台执行的方法
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
// NSOperation 两个子类实现多线程的方法
// 1.NSInvocationOperation Target Action 设计模式
NSInvocationOperation *invocationOperation = [[NSInvocationOperationalloc] initWithTarget:self selector:@selector(test:) object:@"参数"];
// [invocationOperation start];
// [invocationOperation release];
// 2.NSBlockOperation Blcok模式
NSBlockOperation *blockOperation = [NSBlockOperationblockOperationWithBlock:^{
NSLog(@"blockOperation");
}];
// [blockOperation start];
// 3.NSOperationQueue 队列方法自动实现多线程执行任务的方法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 设置同时执行任务数量的最大值
queue.maxConcurrentOperationCount = 1;
// 添加依赖关系,让一个任务等待另一个任务的完成
[invocationOperation addDependency:blockOperation];
// 3.1添加任务到队列中(队列执行顺序是并行执行的并且是无序的)
[queue addOperation:invocationOperation];
[queue addOperation:blockOperation];
[invocationOperation release];
GCD(Grand Central Dispatch)苹果公司开发的技术。以优化应用程序支持多核心处理器和其他的对称多处理系统的系统
GCD属于函数级的多线程,性能更高,功能也更加强大
它首次发布在Mac OS X 10.6,iOS4及以上版本可用
核心概念
具有一定功能的代码段,一般是一个Block或者函数
分发队列:GCD以队列的方式进行工作,FIFO:即First In First Out
GCD会根据分发队列的类型,创建合适数量的线程执行队列中的任务
GCD中的两种队列
dispatch queue分为以下两种
SerialQueue(一次只执行一个任务。通常用于同步访问特定的资源和数据。当你创建多个Serial queue时,虽然他们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。可以实现线程同步)、Concurrent (可以并发地执行多个任务,但是遵守FIFO)
dispatch_async()
//往队列中添加任务,任务会排队执行
dispatch_after()
//往队列中添加任务,任务不但会排队,还会在延迟的时间点执行
dispatch_apply()
//往队列中添加任务,任务会重复执行n次
dispatch_group_() //将任务添加到队列中,并添加分组标记
dispatch_group_notify() //将任务添加到队列中,当某个分组的所有任务执行完毕后,此任务才会执行
dispatch_barrier_async() //将任务添加到队列中,此任务执行的时候,其他任务停止执行
dispatch_once()
//任务添加到队列中,但任务在程序运行过程中,只执行一次
dispatch_sync() //将任务添加到队列中,block不执行完,下面代码不会执行
dispatch_async_f() //将任务添加到队列中,任务是函数非block
// 1. 使用主队列实现任务的派发,串行。主队列分派的任务,永远在主线程中
// 1.1 拿到主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 1.2 添加任务
dispatch_async(mainQueue, ^{
NSLog(@"第一个任务:当前线程是:%@", [NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"第二个任务:当前线程是:%@", [NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"第三个任务:当前线程是:%@", [NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"第四个任务:当前线程是:%@", [NSThread currentThread]);
});
// 2.自己创建队列,串行,即一次只执行一个任务
// GCD会根据分发队列的类型,创建合适数量的线程执行队列中的任务
dispatch_queue_t myQueue = dispatch_queue_create("com.lanou,myqueue",DISPATCH_QUEUE_SERIAL);
// 2.1.添加任务
dispatch_async(myQueue, ^{
NSLog(@"第一个任务是:当前线程是:%@", [NSThread currentThread]);
});
dispatch_async(myQueue, ^{
NSLog(@"第二个任务是:当前线程是:%@", [NSThread currentThread]);
});
dispatch_async(myQueue, ^{
NSLog(@"第三个任务是:当前线程是:%@", [NSThread currentThread]);
});
dispatch_async(myQueue, ^{
NSLog(@"第四个任务是:当前线程是:%@", [NSThread currentThread]);
});
// 3. 自定义队列,并发执行
dispatch_queue_t myQueue2 =dispatch_queue_create("con.lanou.myqueue2",DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myQueue2, ^{
NSLog(@"第一个执行的任务是:当前线程是:%@", [NSThread currentThread]);
});
dispatch_async(myQueue2, ^{
NSLog(@"第二个执行的任务是:当前线程是:%@", [NSThread currentThread]);
});
dispatch_async(myQueue2, ^{
NSLog(@"第三个执行的任务是:当前线程是:%@", [NSThread currentThread]);
});
dispatch_async(myQueue2, ^{
NSLog(@"第四个执行的任务是:当前线程是:%@", [NSThread currentThread]);
});
// // 4.系统并行队列
dispatch_queue_t queue2 =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue2, ^{
NSLog(@"第一个执行的任务:当前线程是:%@", [NSThread currentThread]);
});
dispatch_async(queue2, ^{
NSLog(@"第二个执行的任务:当前线程是:%@", [NSThread currentThread]);
});
dispatch_async(queue2, ^{
NSLog(@"第三个执行的任务:当前线程是:%@", [NSThread currentThread]);
});
dispatch_async(queue2, ^{
NSLog(@"第四个执行的任务:当前线程是:%@", [NSThread currentThread]);
});
// 系统并行队列实例
// 5.获取网络图片数据
__block JYFViewController *viewVC = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 5.1获取网络图片数据
NSURL *url = [NSURLURLWithString:@"http://pic1.win4000.com/wallpaper/6/53bfb7f60d991.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 5.2 显示图片数据
dispatch_async(dispatch_get_main_queue(), ^{
[viewVC showImage:image];
});
});
// 保证代码只被运行一次
for (int i = 0; i < 100; i ++) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"保证代码只被运行一次");
});
}
// 让代码推迟几秒运行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 *NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"推迟五秒");
});
// 在子线程中执行4次
dispatch_apply(4,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_tt){
NSLog(@"打印四次");
});
}
#pragma mark 打印对象的方法
- (void)test:(id)obj
{
NSLog(@"%@", obj);
}
#pragma mark 展示图片
- (void)showImage:(UIImage *)image
{
NSLog(@"%@ %d", [NSThread currentThread], [NSThread isMainThread]);
self.imageView.image = image;
}
线程之间的通信
分为两种:
主线程进入子线程(前面的方法都可以)
子线程回到主线程
返回主线程:
GCD:dispatch_get_main_queue()
NSObject:- (
void)performSelectorOnMainThr
ead:(
SEL)aSelector withObject:(
id)arg waitUntilDone:(
BOOL)wait
线程互斥
线程互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排斥性。
互斥无法限制访问者对资源的访问顺序,即访问是无序的。因此需要加上互斥锁来进行顺序访问,最具有代表性的就是买票系统!
NSLock类能协助完成互斥操作
总结:
NSThread、NSOperationQueue、NSObject、GCD都能实现多线程
GCD是苹果提供的性能更高级的方式
线程尽管提升了性能,但是存在一些访问限制,比如线程同步、线程互斥等