在学习多线程之前我们先来了解几个基本的概念:
1、什么是进程?
进程是指在系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程均运行在其专用且受保护的内部空间内,比如同时打开QQ、Xocde,系统就会分别启动两个进程,通过“活动监视器”,可以查看Mac系统中所开启的进程。
2、什么是线程?
1个进程要想执行任务,必须得有线程(1个进程至少要有1条线程),线程是进程的基本执行单元,一个进程的所有任务都在线程中执行,比如使用酷狗播放音乐,使用迅雷下载电影,都需要在线程中执行。
3、线程的串行
一个线程中任务的执行是串行的,如果要在1个线程中执行多个任务,也就是说在同一时间内,1个线程中只能执行1个任务
4、多线程
4.1 什么是多线程
1个进程中可以开启多条线程,每条线程可以并行执行不同的任务。比如:进程->车间、线程->车间工人
多线程技术可以提高程序的执行效率。比如同时开启3条线程分别下载3个文件
4.2 多线程的原理
同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
思考:如果线程非常非常多,会发生什么情况?
CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源每条线程被调度执行的频次会降低(线程的执行效率降低)
4.3多线程的优缺点
优点:
(1)能适当提高程序的执行效率
(2)能适当提高资源利用率(CPU、内存利用率)
缺点:
(1)开启线程需要占用移动的内存空间(默认情况下,主线程占用1M,子线程占用512KB)如果开启大量的线程,会占用大量的内存空间,降低程序的性能
(2)线程越多,CPU在调度线程上的开销就越大
(3)程序设计更加复杂:比如线程之间的通信、多线程的数据共享
4.4多线程在iOS开发中的应用
主线程:一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”
主线程的主要作用
显示\刷新UI界面
处理UI事件(比如点击事件、滚动事件、拖拽事件等)
主线程的使用注意:别将比较耗时的操作放到主线程中。
耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验
说完基本的概念之后正式进入正题,IOS支持三个层次的线程编程,从底层到高层(层次越高使用越方便,越简洁)分别是:
1:Thread;
2:Cocoa Operations;
3:Grand Central Dispatch;
Thread
一个Thread对象就是代表一条线程
第一种自己启动线程
// 创建线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downLoad) object:nil];
// 设置线程的名字
thread.name = @"线程1";
// 开启线程
[thread start];
// 拿到主线程
[NSThread mainThread];
// 判断是不是主线程
[NSThread isMainThread];
控制台打印,在子线程执行
第二种自动启动
[NSThread detachNewThreadSelector:@selector(downLoad) toTarget:self withObject:nil];
第三种
// 创建隐式线程并启动,不会创建线程,在当前线程中执行
// 优点:简单快捷
// 缺点:无法对线程进行更详细的设置
// [self performSelector:@selector(downLoad:) withObject:@"http://m1.music.126.net/XdP-TUsP9aSZtxAn1af3Pg==/6636652185377132.mp3"];
// 在后台下载
[self performSelectorInBackground:@selector(downLoad:) withObject:@"http://m1.music.126.net/XdP-TUsP9aSZtxAn1af3Pg==/6636652185377132.mp3"];
注意 线程一旦停止(死亡了),就不能再次开启开启任务
多线程的安全隐患
资源共享
解决:
(1)互斥锁 使用前提是多条线程抢夺同一资源
@synchronized
优点:能有效的防止因多线程抢夺资源造成的线程安全问题
缺点:需要消耗大量的CPU资源
线程同步:多条线程在同一条线上执行(按照顺序执行)
互斥锁就是使用线程同步技术
(2)原子和非原子属性
建议:
所有属性都声明为nonatomic
尽量避免多线程抢夺同一块资源
尽量讲加锁、资源抢夺的业务逻辑交给服务器去处理,减小移动客户端的压力
线程中通信
在线程完成下载任务,回到主线程刷新UI界面
// [self performSelectorOnMainThread:@selector(refresh) withObject:nil waitUntilDone:YES];
[self performSelector:@selector(refresh) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES];
GCD
什么是GCD
全称是GrandCentral Dispatch 可翻译为“牛逼的中枢调度器”
纯C语言,提供非常多强大的函数
GCD优势
GCD是苹果公司为多核的并行运算提出得解决方案
GCD会自动利用更多地CPU内核(比如双核、四核)
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
任务和队列
队列用来存放任务,GCD会自动将队列中的任务取出,放到对应的线程中执行
任务的取出遵循队列的FIFO原则:先进先出,后进后出
队列有两种:
并发队列:
可以让多个任务并发(同时)执行(自动开启多个线程同时 任务)
并发功能只能有异步(dispatch——async)函数下才有效
串行队列:
让任务一个接着一个的执行(一个任务执行完毕之后再执行下一个任务)
同步和异步主要影响:能不能开启新的线程
同步,在当前线程执行,不具备开启新线程的能力
异步,创建新的线程
并行和串行主要影响:任务的执行方式
// 全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 同步
// dispatch_sync(queue, ^{
// NSLog(@"下载图片 %@", [NSThread currentThread]);
//
// });
// 异步
dispatch_async(queue, ^{
NSLog(@"下载图片1 %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下载图片2 %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下载图片3 %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下载图片4 %@", [NSThread currentThread]);
});
GCD线程中的通信
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"%@", [NSThread currentThread]);
//1. 在线程下载图片
NSURL *url = [NSURL URLWithString:@""];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 回到主线程
dispatch_async(dispatch_get_main_queue(), ^{
// 设置图片
NSLog(@"%@", [NSThread currentThread]);
NSLog(@"%@", image);
});
});
NSOperation
配合使用NSOperation和NSOperationQueue也能实现多线程
实现步骤:
将要执行的操作封装到一个NSOperation对象中
将NSOperation对象添加到NSOperationQueue中
系统会自动将NSOperationQueue中的NSOperation取出来
将取出的NSOperation封装的操作放到一条线程中执行
NSOperation是个抽象类,并不具备封装操作的能力,必须使用他的子类
3种方式:
NSInvicationOperation
NSBlockOperation
自定义子类继承NSOperation,实现背部相应的方法
最后分享一个面试题
有这样一个需求,当我们在做项目过程中可能需要好多人同时去读取一个数据,但是想要对数据进行修改需要等待没有人进行操作的时候才能进行修改,该怎样实现