本篇文章主要描述了多线程的一方面知识。可能有一些不足之处,希望大家指出。文章最后有一些关于多线程方面的面试题,仅供参考。
本篇文章排版不是很好,可以去看 修改版!
多线程
- 同步&异步
- 进程&线程
- 多线程基本概念
- 多线程的优缺点
同步&异步
1 同步和异步是两种执行任务的方式。
2 同步:代码从上到下顺序执行就叫做同步执行(多个任务依次执行)。
3 异步:多个任务同时执行就是异步执行。
线程&进程
1 进程:在系统中正在运行的一个程序叫做进程,进程可以类比成正在正常运营的公司。
2 线程:线程可以类比成公司里的员工,程序启动默认会开启一个线程。
多线程的基本概念
1 多线程:一个进程中可以开启多条线程,多条线程可以同时执行不同的任务。
2 举个例子:酷我音乐的边下载边听歌,迅雷的边下载边播放。
多线程的优缺点
1 优点:能‘适当’提高程序的执行效率,能适当提高CPU的内存利用率,线程上的任务执行完成后,线程会自动销毁节省内存。
2 缺点:如果开启线程过多会占用大量CPU资源降低程序性能。
多线程的目的
- 将耗时操作放在后台处理,保证UI界面的正常显示和交互。
- 网络操作是非常耗时的,在做网络开发,所有网络访问都是耗时操作.需要在后台线程中执行。
- 多线程开发的原则:越简单越好。
常见的多线程几种方式
- PThread(开发中几乎用不到)
- NSThread(开发中很少使用到)
- GCD(经常使用)
-
NSoperation(经常使用)
GCD
- GCD的概念
- GCD的简单使用
- GCD的任务和队列
GCD的概念
1 什么是GCD:全称是Grand Central Dispatch,纯C语言的,提供了非常多强大的函数。
2 GCD的核心:将任务添加到队列。
3 GCD使用的两个步骤:创建任务,确定要做的事情,GCD中的任务是使用BLOCK封装的。将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行。任务的取出遵循队列的FIFO原则 : 先进先出,后进后出。
GCD的简单使用
1 任务添加到队列
- (void)GCDDemo1
{
// 1. 创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 2. 创建任务 : 用block指定的 (无参无返回值的)
void (^task)() = ^ {
NSLog(@"%@",[NSThread currentThread]);
};
// 3. 把任务添加到队列
// dispatch_async : 表示任务是异步的
// dispatch_sync : 表示任务是同步的
dispatch_async(queue, task);
}
2 简写
- (void)GCDDemo2
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@",[NSThread currentThread]);
});
}
3 线程间的通信
- (void)GCDDemo4
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"假装在努力下载...%@",[NSThread currentThread]);
// 下载结束之后,回到主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"假装在更新UI...%@",[NSThread currentThread]);
});
});
}
4 使用GCD的线程间的通信实现异步下载网络图片
- (void)downloadImage
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"downloadImage %@",[NSThread currentThread]);
// URL
NSURL *URL = [NSURL URLWithString:@"http://atth.eduu.com/album/201203/12/1475134_1331559643qMzc.jpg"];
// data
NSData *data = [NSData dataWithContentsOfURL:URL];
// image
UIImage *image = [UIImage imageWithData:data];
// 拿到图片对象之后,回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"updateUI %@",[NSThread currentThread]);
self.myImageView.image = image;
[self.myImageView sizeToFit];
[self.myScrollView setContentSize:image.size];
});
});
}
GCD的任务和队列
1 GCD的任务:
同步的方式执行任务 : 在当前线程中依次执行任务。
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
异步的方式执行任务 : 新开线程在新线程中执行任务。
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
2 GCD队列:
串行队列:让任务一个接着一个有序的执行:不管队列里面放的是什么任务,一个任务执行完毕后,再执行下一个任务,同时只能调度一个任务执行。
并发队列:可以让多个任务并发/同时执行,自动开启多个线程同时执行多个任务,同时可以调度多个任务执行。并发队列的并发功能只有内部的任务是异步任务时,才有效。
代码小结
1 串行队列+同步任务
/*
1.不开线程
2.有序执行
*/
- (void)GCDDemo1
{
/*
创建串行队列
参数1 : 队列的标识符
参数2 : 队列的属性,决定了队列是串行的还是并行的
DISPATCH_QUEUE_SERIAL : 串行队列
*/
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_SERIAL);
// 循环的创建了10个同步任务,添加到队列
for (NSInteger i = 0; i<10; i++) {
// 把同步任务添加到串行队列
dispatch_sync(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"哈哈哈");
}
2 串行队列+异步任务
- (void)GCDDemo2
{
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_SERIAL);
for (NSInteger i = 0; i<10; i++) {
// 把异步任务添加到串行队列
dispatch_async(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"嘿嘿嘿");
}
3 并行队列+同步任务
/*
不开线程
有序执行
*/
- (void)GCDDemo1
{
// 创建并行队列
// DISPATCH_QUEUE_CONCURRENT : 并行队列
// 并行队列只能决定"是否"可以同时调度多个任务;不能决定开不开线程
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i<10; i++) {
// 把同步任务添加到并行队列
dispatch_sync(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"哈哈哈");
}
4 并行队列+异步任务
/*
开线程
无序执行
*/
- (void)GCDDemo2
{
// 并行队列
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i<10; i++) {
// 把异步任务添加到并发队列
dispatch_async(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"嘿嘿嘿");
}
Paste_Image.png
NSOperation
- NSOperation的简介
- NSOperation的简单使用
- NSOperation的高级功能
NSOperation的简介
1 是OC语言中基于GCD的面向对象的封装,使用起来比GCD更加简单。提供了一些GCD不好实现的功能,苹果推荐使用。NSOperation还不用关心线程和线程的声明周期。
2 NSOperation是个抽象类无法直接使用。因为方法只有声明没有实现。
3 子类:NSInvocationOperation和NSBlockOperation,自定义NSOperation操作默是异步的。
4 队列 : NSOperationQueue队列默认是并发的。
5 核心:GCD的核心 : 将任务添加到队列中。OP的核心 : 将操作添加到队列中。
NSOperation的简单使用
1 先将需要执行的操作封装到一个NSOperation对象中,创建NSOperation对象。
2 将NSOperation对象添加到NSOperationQueue中。
3 NSOperationQueue会自动将NSOperation取出来。
4 将取出的NSOperation封装的操作自动放到一条对应的新线程中执行。
NSOperation的高级功能
1 最大操作并发数。
1>设置最大并发数
// 队列的最大并发数的属性
// 作用 : 控制队列同时调度任务执行的个数;
// 间接控制了线程的数量;
// 注意 : 队列的最大并发数,不是线程数;
@implementation ViewController {
/// 全局队列
NSOperationQueue *_queue;
}
- (void)viewDidLoad {
[super viewDidLoad];
_queue = [[NSOperationQueue alloc] init];
// 设置队列的最大并发数 : 至少开两个
_queue.maxConcurrentOperationCount = 2;
}
2>演示
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self GCDDemo];
}
- (void)GCDDemo
{
for (NSInteger i = 0; i<50; i++) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
}];
[_queue addOperation:op];
}
}
3>执行结果:任务是两个两个的执行。
2 继续/暂停/取消全部。
1>在最大并发数代码的基础上增加暂停、继续、取消。
#pragma 取消全部
/*
1.正在执行的操作无法被取消;
2.如果非要取消正在执行的操作,需要自定义NSOperation
3.这个取消全部的操作有一定的时间延迟
*/
- (IBAction)cancelAll:(id)sender
{
// 移除队列里面"所有"的操作
[_queue cancelAllOperations];
NSLog(@"取消全部 %tu",_queue.operationCount);
}
#pragma 继续
- (IBAction)jixu:(id)sender
{
// 不挂起队列,使队列继续调度任务执行
_queue.suspended = NO;
NSLog(@"继续 %tu",_queue.operationCount);
}
#pragma 暂停
/*
1.正在执行的操作无法被暂停
2.operationCount : 队列里面的操作个数;统计的是队列里面还没有执行完的操作;
3.队列里面的任务一旦执行完,会从队列里面移除;
*/
- (IBAction)zanting:(id)sender
{
// 挂起队列,使队列暂停调度任务执行
_queue.suspended = YES;
NSLog(@"暂停 %tu",_queue.operationCount);
}
2>暂停队列结论
将队列挂起之后,队列中的操作就不会被调度,但是正在执行的操作不受影响。
operationCount:操作计数,没有执行和没有执行完的操作,都会计算在操作计数之内。
注意:如果先暂停队列,再添加操作到队列,队列不会调度操作执行。所以在暂停队列之前要判断队列中有没有任务,如果没有操作就不暂停队列。
3>取消队列结论
一旦调用的 cancelAllOperations方法,队列中的操作,都会被移除,正在执行的操作除外。
正在执行的操作取消不了,如果要取消,需要自定义NSOperation。
队列取消全部操作时,会有一定的时间延迟。
3 操作间依赖关系。
场景:登陆-->付费-->下载-->通知用户
1>准备需要执行的操作
// 登录
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"登录 %@",[NSThread currentThread]);
}];
// 付费
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"付费 %@",[NSThread currentThread]);
}];
// 下载
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载 %@",[NSThread currentThread]);
}];
// 通知用户
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"通知用户 %@",[NSThread currentThread]);
}];
2>添加依赖(核心代码)
/*
添加依赖关系
1.不能在操作添加到队列之后,在建立依赖关系;因为已经晚了
2.可以跨队列建立依赖关系
3.不能建立循环依赖
*/
[op2 addDependency:op1]; // 付费依赖登录
[op3 addDependency:op2]; // 下载依赖付费
[op4 addDependency:op3]; // 通知用户依赖下载
// [op1 addDependency:op4]; // 登录依赖通知用户 : 循环依赖;会卡死
// 批量把操作添加到队列
// waitUntilFinished : 是否等待前面的异步任务执行完,在执行后面的代码
[_queue addOperations:@[op1,op2,op3] waitUntilFinished:NO];
// 一个操作不能同时添加到两个队列
[[NSOperationQueue mainQueue] addOperation:op4];
3>结论
不能循环建立操作间依赖关系,否则队列不调度操作执行。
操作间可以跨队列建立依赖关系。
要将操作间的依赖建立好了之后,再添加到队列中,先建立操作依赖关系,再把操作添加到队列。
面试题
面试题仅供参考。 ><!
- 用 NSOpertion 和 NSOpertionQueue 处理 A,B,C 三个线程,要求执行完 A,B 后才能执行 C, 怎么做?
答: 添加操作依赖,C依赖于A同时依赖于B。创建操作队列,将操作添加到操作队列中。 - 线程间怎么通信?
答:什么是线程通信:不同线程之间传递数据,一般线程传递到主线程。 iOS中开启多线程的方式:三种。比如:在子线程下载图片,然后回到主线程显示图片。 - 简述多线程的作用以及什么地方会用到多线程?OC实现多线程的方法有哪些?谈谈多线程安全问题的几种解决方案?何为线程同步,如何实现的?分线程回调主线程方法是什么,有什么作用?
答:1>耗时操作、界面卡死的时候使用多线程
2.>作用:可以同时执行多个任务,适当提高程序的执行效率。为了提高CPU的使用率,采用多线程的方式去同时完成几件事互不干扰。
3>iOS中多线程的方法:NSThread、NSOperation、GCD、pthread
4>使用场景:同时上传和下载多个文件:加载网络数据同时展示Loading的UI、大量数据I/O操作。
5>资源共享造成的安全问题:多线程环境下,当多个线程同时操作共享资源的setter和getter方法时,会造成数据的读写错乱就是线程安全问题。
6>线程同步技术:使多个线程一次有序的执行,实现方案是加锁,把共享资源的读写操作锁起来常用的是互斥锁。
7>线程间的通信:一个线程执行完任务之后,把执行的结果传递到另外一个线程叫线程间通信。线程间通信可用来在两个线程间传递数据。
网友评论
求教~
- (void)test1 {
dispatch_queue_t queue = dispatch_queue_create("queue",DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
//同步任务1
[self test2:queue];
NSLog(@"haha");
});
}
- (void) test2:(dispatch_queue_t)queue {
dispatch_sync(queue, ^{
//同步任务2
NSLog(@"xixi");
});
}
如果你在同步任务1中再执行同步任务2,串行队列queue 中先后添加了同步任务1,同步任务2,当执行到同步任务2时它需要等待之前的同步任务1执行完才能执行,而同步任务1也需要等待同步任务2执行完才能继续执行,从而导致了死锁,这跟主队列放同步任务会产生死锁是类似的。而主队列特殊性是主队列是系统默认创建好了的