进程\线程
-
进程
- 进程 是指在系统中正在运行的一个应用程序。
- 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内。
-
线程
- 1个进程由多个线程组成(1个进程至少要有1个线程)。
- 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行。
多线程
-
多线程
- 1个进程中可以开启多个线程,多个线程可以同时执行不同的任务。
- 多线程可以提高程序的执行效率。
-
多线程的原理
- 对于单核CPU来说,同一时间,CPU只能处理1个线程,只有1个线程正在执行。
- 多线程同时执行的本质:是CPU快速的在多个线程之间的切换。
- CPU调度线程的时间足够快,就造成了多线程的“同时”执行。
- 如果线程数非常多,CPU会在n个线程之间切换,消耗大量的CPU资源,每个线程被调度的次数会降低,线程的执行效率降低。
-
多线程的优点
- 能适当提高程序的执行效率。
- 能适当提高资源的利用率(CPU、内存)。
- 线程上的任务执行完成后,线程会自动销毁。
-
多线程的缺点
- 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能。
- 线程越多,CPU在调用线程上的开销就越大。
- 程序设计更加复杂,比如线程间的通信、多线程的数据共享。
-
主线程
- 一个程序运行后,默认会开启1个线程,称为“主线程”或“UI线程”。
- 主线程一般用来刷新UI界面,处理UI事件。
- 主线程使用注意:别将耗时的操作放到主线程中,因为耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种卡的坏体验。
同步和异步
-
同步执行
- 写程序的时候都是从上到下,从左到右,代码执行顺序也是从上到下从右到左。
- 1个线程执行多个任务,也是依次执行a->b->c。
- 1个线程同一时间执行1个任务。
-
异步执行
- 多个线程可以同时执行多个任务。
- 多个线程执行多个任务可以同时执行a,b,c。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 异步执行
[self performSelectorInBackground:@selector(test) withObject:nil];
NSLog(@"over");
}
// 演示耗时操作
- (void)test {
for (int i = 0; i < 1000 ; i++) {
// 操作栈空间 速度特别快
// int n = 10;
// 操作堆空间,速度慢
// NSString *str = [NSString stringWithFormat:@"abc %d",i];
// io操作 input output
NSLog(@"abc");
}
}
iOS中多线程的实现方案
-
技术方案
- pthread,C语言的一套通用的多线程API,适用于UNIX\Linux\Windows等系统,跨平台\可移植,使用难度大,线程生命周期需要程序员管理。
- NSThread,OC语言,使用更加面向对象,简单易用,可直接操作线程对象,线程生命周期需要程序员管理。
- GCD,C语言,旨在替代NSThread等线程技术,充分利用设备的多核,线程生命周期自动管理。
- NSOperation,基于OC语言(底层是GCD),比GCD多了一些更简单实用的功能,使用更加面向对象,线程生命周期自动管理。
-
pthread的使用
- 使用pthread需要导入头文件
#import <pthread.h>
。 - 导入的头文件是跨平台的,都是C语言的。
- 通过
intpthread_create()
函数创建线程。 - 在ARC中,使用到和C语言对应的数据类型,应该使用
__bridge
桥接,在MRC中不需要桥接。在OC中,如果是ARC的话,编译的时候会自动添加retain、release、autorelease,ARC只负责OC的代码,不负责C的代码,如果C语言的框架中,出现create、retain、copy,需要release,而桥接的目的就是解决这个问题。
- 使用pthread需要导入头文件
int pthread_create(pthread_t * __restrict,
const pthread_attr_t * __restrict,
void *(*)(void *),
void * __restrict);
// 参数:
// 第1个参数为指向线程标示符的指针(线程编号的地址)
// 第2个参数用来设置线程属性
// void *(*)(void *) 返回值 (函数指针)(参数)
// void * 返回值类型,类似于OC中的id
// (*) 函数的指针
// (void *) 参数列表
// 第3个参数是线程调用函数指针
// 第4个参数是运行函数的参数
// 返回值:
// 返回0 线程创建成功 此时第1个参数是新线程的编号
// 非0 线程创建失败 返回对应错误码
pthread的使用
- NSThread
- NSThread线程的执行是异步的。
- 面向对象的方式创建子线程。
// 通过NSThread开启1个子线程的3种方式
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg;
NSThread的使用
- 线程的状态
- 当启动线程后,线程的方法并没有立即执行,而是进入就绪状态,等待CPU的调用。
- 线程的状态分为新生状态、就绪状态、运行状态、死亡状态、阻塞状态。
- 终止线程前,要释放之前分配的内存。如果是ARC,需要清理C语言框架创建的对象,否则会出现内存泄露。
- 线程常用属性
- 线程名称:设置线程名称可以当线程执行的方法内部出现异常的时候记录异常和当前线程。
- 线程优先级:内核调度算法在决定该运行哪个线程时,会把线程的优先级作为考量因素,较高优先级的线程会比较低优先级的线程具有更多的运行机会。较高优先级不保证你的线程具有执行的时间,只是相比较低优先级的线程,它更有可能被调度器选择执行而已。
多线程访问共享资源的问题
- 互斥锁(线程同步)
- 如果多个线程同时读写一个公共变量(属性),可能会出现问题。
- 为解决上面的问题,在代码中加入互斥锁(线程同步):
- 互斥锁保证锁内的代码同一时间,只有一个线程能够执行锁住的代码块。
- 互斥锁的参数必须是一个对象,任意一个对象都可以,但是不能是线程执行方法中定义的对象,一般用self即可。
- 互斥锁的原理:每一个对象(NSObject)内部都有一个锁(变量),当有线程要进入@synchronized到代码块中会先检查对象的锁是打开还是关闭状态,默认锁是打开状态(1),如果是线程执行到代码块内部会先上锁(0)。如果锁被关闭,再有线程要执行代码块就先等待,直到锁打开才可以进入。
- 加锁后程序执行的效率比不加锁的时候要低,因为线程要等待锁,但是锁保证了多个线程同时操作全局变量的安全性。
未使用互斥锁 多线程同时访问共享资源引起的问题线程执行到@synchronized
① 检查锁的状态 如果是开锁状态(1)转到② 如果上锁(0)转到⑤
② 上锁(0)
③ 执行代码块
④ 执行完毕 开锁(1)
⑤ 线程等待(就绪状态)
使用互斥锁解决多线程访问共享资源的问题
-
原子属性
- 属性中的修饰符:
- nonatomic:非原子属性。
- atomic:原子属性(线程安全),针对多线程设计的,属性的修饰符默认值就是atomic。保证同一时间只有一个线程能够写入(但是同一时间多个线程都可以取值)。atomic本身就是一把锁(自旋锁)。单写多读:单个线程写入,多个线程可以读取。
- 自旋锁和互斥锁:
- 相同点:都能保证同一个时间只有一个线程能够执行锁定范围内代码。
- 不同点:①互斥锁:如果发现其他线程正在执行锁定代码,线程会进入休眠(就绪状态),等其它线程时间片到打开锁后,线程会被唤醒(执行)。
- 自旋锁:如果发现有其它线程正在锁定代码,线程会用死循环的方式,一直等待锁定的代码执行完成,自旋锁更适合执行不耗时的代码。
- 无论什么锁,性能都会有损耗(用性能换取数据安全性)。
- 属性中的修饰符:
-
线程安全
- 线程是不安全的,多个线程同时操作同一个全局变量。
- 线程安全:在多个线程进行读写操作时,仍然能够保证数据的正确。
- UI线程(主线程):
- 几乎所有UIKit提供的类都不是线程安全的。
- 所有更新UI的操作都在主线程上执行。
- Mutable线程是不安全的。
网友评论