什么是进程?
进程是指系统中正在运行的一个程序,每个进程间是独立的,每个进程均运行在其专用且受保护的内存空间内.
什么是线程?
一个进程想要执行任务,必须得有线程.(每一个进程至少要有一个线程)
一个进程中的所有任务都在线程中执行
线程的串行
一个线程中的所有任务的执行顺序都是串行的,也就是说,在同一时间内,一个线程只能执行一个任务.只有执行完上一个任务才可以执行下一个;也可以任务,线程是进程中的一条执行路径
什么是多线程?
一个进程中可以开启多条线程,每条线程可以并行执行不同的任务
多线程技术可以提高程序的执行效率
多线程原理
同一时间,1核CPU只能处理一条线程,只有一条线程在工作;
多线程并发(同时)执行,其实就是CPU快速的在多线程之间调度(切换);
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象;
如果线程开的非常多
CPU会在N条线程之间调度,CPU会累死,消耗大量CPU资源;
每条线程被调度执行的频次降低,线程的执行效率会降低
多线程的优缺点
优点:能适当提高程序的执行效率;能适当提高资源利用率 (CPU资源利用及内存利用)
缺点:创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB),栈空间(子线程512KB,主线程1MB,也可以使用-setStackSize设置,但必须是4K的倍数,而且最小必须是16K), 创建线程大约需要90毫秒的时间;如果开启大量的线程,就会降低程序的性能;线程越多,CPU在调度线程上的开销就越大;程序设计更加复杂,比如线程之间的通信,多线程的数据共享
什么是主线程?
一个iOS程序运行后,默认会开启一条线程,称为”主线程”或”UI线程”
主线程的主要作用:显示\刷新界面;处理UI事件,如触摸,点击,拖拽,滑动;
使用注意:耗时操作不要放在主线程,耗时操作会卡住主线程,严重影响UI的流畅度,会给用户造成App假死现象
苹果开发,约定刷新UI的操作需要放在主线程! 其实刷新UI的操作放到子线程不一定会崩溃,但是往往会发生一些意想不到且很难找出原因的问题!
iOS多线程的实现方案 有4中方式:
phtread :C语言开发,线程周期需要程序员管理,因为调用的是函数,实际开发一般用不到;
NSThread:OC语言开发,使用可直接操作线程对象,线程周期需要程序员管理,实际开发使用较少;
GCD:C语言开发,自动管理线程周期,开发中经常使用;
NSOperation:OC语言开发,基于GCD(其实NSOperation比GCD出来早,后来苹果貌似做了底层重写),使用更加面向对象,开发中经常使用
NSThread 使用
一个NSThread对象就代表一条线程
创建一个NSThread
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullableid)argument;
返回一个NSThread对象,可以对线程进行管理操作
selector: 调用的方法
argument:给调用方法传递的参数
- (void)start; //启动线程 线程一启动,就会在线程thread中执行self的selector方法
主线程相关用法
+ (NSThread*)mainThread;//获得主线程
- (BOOL)isMainThread;//是否为主线程
+ (BOOL)isMainThread;//是否为主线程
[NSThread currentThread]; //获得当前线程
thread.name=@"customThread"; //给线程设置名称,程序崩溃的时候,能够获取都程序崩溃所在线程
+ (void)detachNewThreadWithBlock:(void (^)(void))block;
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullableid)argument;
从线程中分离出一条新的线程来执行
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullableid)arg;
InBackground 在后台运行,即子线程运行;是NSObject的分类, 意味着所有继承于NSObject的对象都可以使用这个方法
上述2种创建线程方式的优缺点
优点:简单快捷
缺点:无法对线程进行更详细的设置
控制线程状态
- (void)start;//启用线程 进入就绪状态->运行状态.当线程任务执行完毕,自动进入死亡状态
+ (void)sleepUntilDate:(NSDate*)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//阻塞线程 进入阻塞状态
+ (void)exit;
//强制停止线程, 会使线程进入死亡状态 注意:一旦线程停止(死亡),就不能再次开启任务
多线程的使用,有资源共享的安全隐患
多个线程可能会访问同一块资源
比如多个线程访问同一个对象、同一个变量、同一个文件
当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题
解决方案:
1.互斥锁 --保证锁内的代码,同一时间,只有一条线程运行
@synchronized(锁对象) {//需要锁定的代码 }
互斥锁的范围,应该尽量小,如果大了,效率就会差
参数可以是任意的OC对象,一般用 self 全局对象
局部变量,是每一个线程单独拥有的,因此没法加锁
优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的CPU资源
2.原子属性 --atomic
原子属性原理:先把文件保存在一个临时的文件中,等全部写入之后,再改名
原子属性的目的:多个线程写入这个对象的时候,保证同一时间只有一个线程能够执行!
单写多读的一种多线程技术,同样有可能出现"脏数据",重新读一下.
实际上,原子属性内部有一个锁,自旋锁
自旋锁 & 互斥锁
- 共同点:
都能够保证线程安全.
- 不同点:
互斥锁:如果线程被锁在外面,哥么就会进入休眠状态,等待锁打开,然后被唤醒!
自旋锁:如果线程被锁在外面,哥么就会用死循环的方式,一直等待锁打开!
无论什么锁,都很消耗新能.效率不高
3.线程同步
多条线程在同一条线上执行(按顺序地执行任务)
线程间通讯
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullableid)arg waitUntilDone:(BOOL)wait;
耗时操作执行完毕,需要回主线程更新UI 就用到了线程间通讯
wait:让当前线程等待 (如果当前线程是主线程!设置YES是没有用的)