iOS系统 中,每一个应用都是一个进程。具体了解Runloop底层原理:https://www.jianshu.com/p/9cb4edc0670d,除了Runloop底层原理还介绍了线程间的通讯等。
进程与线程
- 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行
- 进程要想执行任务,必须得有线程,进程至少要有一条线程
- 程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程
- 进程是指在系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存。
进程与线程的关系
- 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
- 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
- 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
- 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
- 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
- 线程是处理器调度的基本单位,但是进程不是。
多线程的意义
多线程的原理其实就是CPU在单位时间片里快速在各个线程之间切换。一般情况下无论多核还是单核,我们的线程运行总是 “并发” 的,这时候我们所说的”并发”是一种模拟出来的状态,CPU在单位时间片里快速在各个线程之间切换,每个线程执行一小段时间,让多个线程看起来就像在同时运行。只有当cpu数量大于等于线程数量,这个时候是真正并发,可以多个线程同时执行计算。
优点
- 能适当提高程序的执行效率。
- 能适当提高资源的利用率(CPU、内存)。
- 线程上的任务执行完成后,线程会自动销毁。
缺点
- 开启线程需要占用一定的内存空间(默认情况下,每一个线程占用512KB)。
- 如果开启大量的线程,会占用大量的内存空间,降低程序的性能。
- 线程越多,CPU在调度线程上的开销就越大。
- 程序设计更加复杂,比如线程间的通讯、多线程的数据共享。
线程的生命周期
线程在创建后,start开始进入Runnable就绪的状态,这时会执行很多初始化的操作;接下进入running状态,CPU会调度当前线程,如果线程池中有当前线程会直接执行,在时间片的影响后CPU再次调度其他线程,直到当前线程任务执行完毕或强制退出,或者堵塞(调用sleep、等待同步锁或者从可调度线程池中移除)结束再次回到Runnable状态。
多线程相关补充
多线程技术方案
线程池
线程池可以使线程得到复用,所谓线程复用就是线程在执行完一个任务后并不被销毁,该线程可以继续执行其他的任务。在线程池大小小于核心线程池大小的时候,如果小于则直接创建线程执行任务。如果超出核心线程池大小则依赖队列,此时线程池会判断当前队列是不是已经满了,如果没有满,则提交任务到工作队列中,等待线程池调度执行任务。如果满了,并且当前工作队列所依赖的线程没有执行工作,那么则可以利用当前线程执行任务,如果此时线程都在工作,接下来会交给饱和策略。饱和策略一般默认都是中止策略,调用者可以捕获到该异常;还有抛弃策略,会悄悄抛弃该任务,不过一般会抛弃最旧的任务或者优先级比较低的任务等;还有调用者运行策略,实现了一种机制,这个机制不会抛弃任务也不会抛出异常,而是将任务回退到调用者,来达到降低新任务的流量。还有等待策略,也就是需要排队等候执行。
线程安全—锁
在开发高性能程序的时候几乎都会用到多线程,但是用到多线程也会碰到一些安全问题。比如多个线程同时对一块内存发生读和写的操作,或者程序执行的顺序会被打乱,可能造成提前释放一个变量,造成计算结果错误等,所以我们经常会用到锁。我们用锁来保证代码操作的原子性,让多线程对同一个数据或者资源进行访问同步。
锁的分类
这里简单说下锁,根据锁的状态、锁的特性和锁的设计等分为:
- 公平锁/非公平锁
- 可重入锁—又名递归锁,一定程度上避免死锁。
- 独享锁/共享锁
- 互斥锁/读写锁
- 乐观锁/悲观锁
- 分段锁
- 偏向锁/轻量级锁/重量级锁
- 自旋锁
atomic与nonatomic
说到原子性就会想到属性关键字中atomic和nonatomic。设置atomic之后,默认生成的getter和setter方法执行是原子的,它只保证了自身的读/写操作,却不能说是线程安全。
- nonatomic 非原子属性
- atomic 原子属性(线程安全),针对多线程设计的,默认值
- 保证同一时间只有一个线程能够写入(但是同一个时间多个线程都可以取值)
- atomic 本身就有一把锁(自旋锁)
- 单写多读:单个线程写入,多个线程可以读取
atomic:线程安全,需要消耗大量的资源
nonatomic:非线程安全,适合内存小的移动设备
我们可以用读写锁来解决,例如:
// 在 OC 中,如果同时重写 了 setter & getter 方法,系统不再提供 _成员变量,需要使用合成指令
// @synthesize name 取个别名:_name
@synthesize name = _name;
- (NSString *)name {
return _name;
}
- (void)setName:(NSString *)name {
/**
* 增加一把锁,就能够保证一条线程在同一时间写入!
*/
@synchronized (self) {
_name = name;
}
}
互斥锁小结
- 保证锁内的代码,同一时间,只有一条线程能够执行!
- 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
互斥锁参数
- 能够加锁的任意 NSObject 对象
- 注意:锁对象一定要保证所有的线程都能够访问
- 如果代码中只有一个地方需要加锁,大多都使用 self,这样可以避免单独再创建一个锁对象
关于线程安全问题,多线程安全比多线程性能更重要,建议用@synchronized
,NSLock
,可保证可读性和安全性。
线程的创建
/**
线程创建的方式
*/
- (void)creatThreadMethod{
NSLog(@"%@", [NSThread currentThread]);
//A: 1:开辟线程
NSThread *t = [[NSThread alloc] initWithTarget:self.p selector:@selector(study:) object:@3];
// 2. 启动线程
[t start];
t.name = @"学习线程";
// detach 分离,不需要启动,直接分离出新的线程执行
[NSThread detachNewThreadSelector:@selector(study:) toTarget:self.p withObject:@5];
//NSObject (NSThreadPerformAdditions)的分类
//C : `隐式`的多线程调用方法,没有thread,也没有 start
self.p = [[Person alloc] init];
[self.p performSelectorInBackground:@selector(study:) withObject:@10];
// GCD
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self study;];
});
// NSOperation
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
[self threadTest];
}];
NSLog(@"%@", [NSThread currentThread]);
}
Person.m文件实现
- (void)study:(id)time{
for (int i = 0; i<[time intValue]; i++) {
NSLog(@"%@ 开始学习了 %d分钟",[NSThread currentThread],i);
}
}
- (void)study:(id)time{
for (int i = 0; i<[time intValue]; i++) {
NSLog(@"%@ 开始学习了 %d分钟",[NSThread currentThread],i);
}
}
或者
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//0: pthread
/**
pthread_create 创建线程
参数:
1. pthread_t:要创建线程的结构体指针,通常开发的时候,如果遇到 C 语言的结构体,类型后缀 `_t / Ref` 结尾
同时不需要 `*`
2. 线程的属性,nil(空对象 - OC 使用的) / NULL(空地址,0 C 使用的)
3. 线程要执行的`函数地址`
void *: 返回类型,表示指向任意对象的指针,和 OC 中的 id 类似
(*): 函数名
(void *): 参数类型,void *
4. 传递给第三个参数(函数)的`参数`
返回值:C 语言框架中非常常见
int
0 创建线程成功!成功只有一种可能
非 0 创建线程失败的错误码,失败有多种可能!
*/
// pthread
pthread_t threadId = NULL;
//c字符串
char *cString = "HelloCode";
// OC prethread -- 跨平台
// 锁
int result = pthread_create(&threadId, NULL, pthreadTest, cString);
if (result == 0) {
NSLog(@"成功");
} else {
NSLog(@"失败");
}
// GCD
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self threadTest];
});
// NSOperation
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
[self threadTest];
}];
}
- (void)threadTest{
NSLog(@"begin");
NSInteger count = 1000 * 100;
for (NSInteger i = 0; i < count; i++) {
// 栈区
NSInteger num = i;
// 常量区
NSString *name = @"zhang";
// 堆区
NSString *myName = [NSString stringWithFormat:@"%@ - %zd", name, num];
NSLog(@"%@", myName);
}
NSLog(@"over");
}
void *pthreadTest(void *para){
// 接 C 语言的字符串
// NSLog(@"===> %@ %s", [NSThread currentThread], para);
// __bridge 将 C 语言的类型桥接到 OC 的类型
NSString *name = (__bridge NSString *)(para);
NSLog(@"===>%@ %@", [NSThread currentThread], name);
return NULL;
}
TIP:C与OC的桥接
- __bridge只做类型转换,但是不修改对象(内存)管理权;
- __bridge_retained(也可以使用CFBridgingRetain)将Objective-C的对象转换为Core Foundation的对象,同时将对象(内存)的管理权交给我们,> 后- 续需要使用CFRelease或者相关方法来释放对象;
- __bridge_transfer(也可以使用CFBridgingRelease)将Core Foundation的对象转换为Objective-C的对象,同时将对象(内存)的管理权交给ARC。
该文章为记录本人的学习路程,也希望能够帮助大家,知识共享,共同成长,共同进步!!!