多线程基础知识
一. 多线程介绍
1 进程:进程是指在系统中正在运行的一个应用程序。
比如同时打开微信、QQ,系统就会分别启动两个进程。
每个进程之间是独立的且均运行在其专用的并受保护的内存空间内。
2 线程:线程是进程的基本执行单元,一个进程要想执行任务,必须得有线程(每一个进程至少要有一个线程)。
比如用微信进行视频聊天、QQ进行文字聊天,都需要在线程中执行。
3 线程的串行:如果要在一个线程中执行多个任务,那么只能一个一个的按顺序执行这些任务,也就是说,同一时间内,一个线程只能执行一个任务,当一个任务执行完成,才能执行另外一个任务。
4 多线程:一个进程中可以开启多条线程,每个线程可以并行(同时)执行不同的任务。、
5 多线程原理:
(1)同一时间,CPU只能处理一条线程,只有一条线程在工作;
(2)多线程并发执行时,实际上是CPU快速的在线程之间调度;
(3)如果CPU调度的足够快,就造成了多条线程并发执行的假象。
如果线程非常非常多,会发生什么情况了?
CPU会在多个线程之间调度,最终会导致CPU累死,消耗大量的CPU资源;并且每条线程被调执行的频率降低,导致线程的执行效率降低。
6 多线程优缺点:
优点:
(1)能够适当的提高程序的执行效率
(2)能够适当提高资源的利用率(CPU、内存的利用率)
缺点:
(1)开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用521KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
(2)线程越多,CPU在调度线程上的开销就越大
(3)程序设计更加复杂:比如线程之间的通信,多线程的数据共享等。
7 主线程:一个iOS程序运行后,默认会开启1条线程,该线程称为“主线程”或“UI线程”。
作用:(1)显示、刷新UI界面 (2)处理UI事件(比如点击、滚动、拖拽事件等)。
注意:不要将比较耗时的操作放置在主线程中。
8 iOS中多线程的使用方案
(1)NSThread
简介:面向对象的使用;简单易用,可以直接操作线程对象。
OC语言
线程的生命周期:程序员手动管理
使用频率:偶尔使用
(2)GCD
简介:旨在代替NSThread等线程技术;充分利用设备的内核。
C语言
线程的生命周期:自动管理
使用频率:经常使用
(3)NSOperation
简介:基于GCD;使用更加面向对象;比GCD多了一些更简单使用的功能。
OC语言
线程的生命周期:自动管理
使用频率:经常使用
二. NSThread的三种创建方式
- (void)createThread1 { NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"123"]; thread.name = @"zijie"; //手动启动线程 [thread start]; } - (void)createThread2 { // 会自动启动线程 [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"456"]; } - (void)createThread3 { // 开启一个后台线程(子线程) [self performSelectorInBackground:@selector(run:) withObject:@"back"]; } - (void)run:(id)obj { NSLog(@"-----%@-----%@", [NSThread currentThread], obj); for (int i = 0; i < 10000; i ++) { NSLog(@"%d", i); } }
三. 线程状态(线程的生命周期)
1 以画图的形式讲解线程状态
2 用代码形式实现线程sleep和异常退出
- (void)run:(id)obj { NSLog(@"-----%@-----%@", [NSThread currentThread], obj); // 第一种休眠方式 [NSThread sleepForTimeInterval:2]; // 第二种休眠方式 [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]]; NSLog(@"再次执行"); // 强制退出线程 for (int i = 0; i < 10000; i ++) { NSLog(@"-----%d------", i); if (i == 100) { // 线程退出 [NSThread exit]; } } }
四. 线程安全
互斥锁:互斥锁又叫同步锁。
优点:有效的防止因多线程抢夺资源造成的数据安全问题。
缺点:因为线程等待,需要消耗大量的CPU资源。
线程同步:
线程同步指的是多条线程在同一条线上执行(按顺序的执行任务)。
添加同步锁:
@synchronized (self){
}
#import "ViewController.h" @interface ViewController () @property (nonatomic, strong) NSThread *thread1; @property (nonatomic, strong) NSThread *thread2; @property (nonatomic, strong) NSThread *thread3; @property (nonatomic, assign) NSInteger tickets; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.tickets = 1000; _thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets:) object:nil]; _thread1.name = @"小张"; _thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets:) object:nil]; _thread2.name = @"小李"; _thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets:) object:nil]; _thread3.name = @"小王"; } - (void)sellTickets:(id)obj { while (self.tickets > 0) { @synchronized (self) { NSInteger curretTickets = self.tickets; if (curretTickets > 0) { NSLog(@"%@卖了一张票,还剩%d张票", [NSThread currentThread].name, --self.tickets); } else { NSLog(@"票已卖完"); } } } } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [_thread1 start]; [_thread2 start]; [_thread3 start]; }
@end
五. NSThread线程通信
1 在一个线程中,线程往往不是孤立存在的,多个线程之间是存在有通信关系的。
2 线程通信的体现:
(1) 一个线程传递数据给另外一个线程
(2) 一个线程执行完成任务后转到另外一个线程继续执行任务
3 由子线程回到主线程的三种方法
// 由子线程回到主线程-----在主线程给一个UIImageView赋值 [self performSelectorOnMainThread:@selector(loadImage:) withObject:image waitUntilDone:YES]; [self performSelector:@selector(loadImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES]; [imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
六. GCD( http://www.cnblogs.com/muzijie/p/6437827.html)
1 什么是GCD
(1)GCD的全称是Grand Central Dispatch(大中枢派发)
(2)纯C语言,提供了非常强大的函数
2 GCD的优点
(1)GCD是苹果公司为多核的并行运算提出的解决的方案
(2)GCD会自动利用更多的CPU内核(比如双核、四核)
(3)GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
(4)程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
3 GCD中有两个核心概念
(1)任务:执行什么操作
(2)队列:用来存放任务
4 GCD的使用就2个步骤
(1)定制任务-------确定想做的事情
(2)将任务添加到队列中--------GCD会自动将队列的任务取出,放到相应的线程中执行
注意:任务的取出遵循队列的FIFO原则:先进先出,后进后出。
5 任务的执行方式
(1)同步的执行方式
dispatch_sync(dispatch_queue-t queue, dispatch_block_t block);
queue: 队列
block: 任务
(2)异步的执行方式
dispatch_async(dispatch_queue-t queue, dispatch_block_t block);
6 同步和异步的区别
同步:只能在当前线程中执行任务,不具备开启新线程的能力
异步:可以在新的线程执行任务,具备开启新线程的能力
7 队列的类型
(1)并发队列:
可以让多个任务并发(同时)执行(自动开启多分线程同时执行)
并发的功能只有在异步函数(dispatch_async)下才有效
(2)串行队列
让任务一个接着一个的执行(也就是说必须等一个任务执行完毕后才可以执行下一个任务)
8 同步和异步的主要影响:能不能开启新的线程
同步------在当前线程中执行任务,不具备开启新线程的能力
异步------可以在新线程中执行任务,具备开启新线程的能力
9 并发和串行的主要影响:任务的执行方式
并发-------多个任务并发执行
串行-------一个任务完成之后,再执行下一个任务
七. GCD队列任务的执行
并发队列 + 同步任务: 没有开启新的线程,任务是逐个执行的。
并发队列 + 异步任务: 开启了新线程,任务是并发的。
全局队列 + 同步任务: 没有开启新的线程,任务是逐个执行的。
全局队列 + 异步任务: 开启了新线程,任务是并发的。
串行队列 + 同步任务: 没有开启新的线程,任务是逐个执行的。
串行队列 + 异步任务: 开启了一个新的线程,任务是逐个完成的。
主队列 + 同步任务:会造成死锁的现象,切记: 不能在主队列增加同步任务。
主队列 + 异步任务:没有开启新的线程,任务逐个完成。
八. GCD线程通信
// 由子线程回归到主线程 dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"1111111"); }); NSLog(@"22222222"); // 打印结果: 2017-02-27 15:33:05.351 UsingNSThread[80021:9372923] 22222222 2017-02-27 15:33:05.352 UsingNSThread[80021:9372923] 1111111 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"1111111"); }); NSLog(@"22222222"); // 打印结果: 2017-02-27 15:33:05.351 UsingNSThread[80021:9372923] 1111111 2017-02-27 15:33:05.352 UsingNSThread[80021:9372923] 22222222
九. NSOperation(http://www.cnblogs.com/muzijie/p/6438160.html)
1 NSOperation作用:
配合使用NSOperation和NSOperationQueue来实现多线程
2 NSOperation和NSOperationQueue使用多线程的步骤
(1)先将需要执行的操作封装到一个NSOperation对象中
(2)将NSOperation对象添加到NSOperationQueue中
(3)系统会自动将NSOperationQueue中的NSOperation取出
(4)将取出的NSOperation封装的对象放到线程中去执行
十. NSOperation线程通信
// 从子线程回归到主线程 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLog(@"---1----"); }];
十一. RunLoop
1. RunLoop概念
(1)字面意思:运行循环(跑圈)
(2)内部实现:内部是由do-while循环来实现的
2. RunLoop的作用
(1)保证程序的持续运行
(2)处理App的各种事件(滑动、点击、定时器、selector)
(3)节省CPU资源,提高程序性能(可以使主线程在有事情做的时候,处理事情;没有事情做的时候,处于休眠的状态)
3. RunLoop对象
iOS提供了2套API来访问和使用RunLoop
(1)Foundation框架下的NSRunLoop(OC语言)
(2)Core Foundation框架下的CFRunLoopRef(C语言)
NSRunLoop和CFRunLoopRef都代表RunLoop,它们的联系是NSRunLoop是基于CFRunLoopRef的。也就是说,要研究NSRunLoop。还是需要研究CFRunLoopRef。
4. RunLoop与对象
(1)每个线程都有唯一的一个与之对应的RunLoop对象
(2)主线程的RunLoop随着程序已创建好,而子线程的RunLoop需要手动创建。
(3)获得主线程RunLoop的方法是[NSRunLoop mainRunLoop]
(4)创建子线程的RunLoop的方法是[NSRunLoop currentRunLoop]
注意:苹果不允许创建RunLoop,只提供了上面两种方法来获取RunLoop。
5. RunLoop相关类
若没有以下这几类,RunLoop是不会循环的。
(1)CFRunLoopModeRef
CFRunLoopModeRef代表了RunLoop的运行模式;
一个RunLoop可以包含若干个Mode,每个Mode可以包含若干个Source、Timer、Observer;
每次RunLoop启动时,只能指定其中的一个Mode,这个Mode被称为CurrentMode;
如果需要切换Mode,需要退出RunLoop,再重新指定一个Mode进入。这么做的目的是为了分离不同组的Source、Timer、Observer,让其互不影响。
CFRunLoopModeRef类型:系统默认注册了5个Mode
(I)kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在该Mode下执行的。
(II)UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时,不受其它Mode影响。
(III)UIIntializationRunLoopMode:在刚启动时,App进入的第一个Mode,启动完成后就不再使用了。
(IV)GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常情况下不使用。
(V)kCFRunLoopCommonModes:这是一个占位用的Mode,不是一个真正的Mode。
CommonModes是一个标记。有这个标记的模式有NSRunLoopDefaultMode、UITrackingRunLoopMode。
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; // 将Mode设置为NSRunLoopCommonModes,此时timer在NSRunLoopDefaultMode和UITrackingRunLoopMode这两种模式下都会运行 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
(2)CFRunLoopTimerRef
CFRunLoopTimerRef是基于时间的触发器;在这里,基本上可以说是NSTimer。
(3)CFRunLoopSourceRef
CFRunLoopSourceRef是一个事件源,也可称为输入源。
按官方文档分类的话,可以分为3类:
(I)Port-Port-Based Sources:从其它线程或者内核发出的;
(II)Custom Input Sources:自定义的;
(III)Cocoa Perform Selector Sources
按函数调用栈分类的话,可以分为2类:
(I)Sources0:非基于Port的;---按钮的点击事件
(II)Sources1:基于Port,通过线程或内核通信,接受、分发系统事件。
(4)CFRunLoopObserverRef
CFRunLoopObserverRef是观察者,能够监听RunLoop状态的变化。
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即将进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), //即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), //即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), //即将从休眠中唤醒
kCFRunLoopExit = (1UL << 7), //即将退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU //活跃中
};
6. RunLoop逻辑处理