线程定义 (雇员)
- 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行
- 进程要想执行任务,必须得有线程,进程至少要有一条线程
- 程序启动会默认开启一条线程,这条线程被称为主线程或UI线程
进程定义(奶茶店)
- 进程是指在系统中正在运行的一个应用程序
- 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存
线程和进程的关系与区别
- 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
- 资源拥有:同一进程内的线程共享本进程的资源,如,内存、I/O、CPU等,但是进程之间的资源是独立的
- 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉,所以多进程要比多线程健壮
- 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程,同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
- 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
- 线程是处理器调度的基本单位,但是进程不是
**扩展:**苹果是单进程,沙盒资源会更安全,切换进程消耗资源会特别大,所以采用单进程体验更流畅
多线程的意义(收银员的意义)
优点
- 能适当提高程序的执行效率
- 能适当提高资源的利用率(CPU,内存)
- 线程上的任务执行完成后,线程会自动销户
缺点
- 开启线程需要占用一定的内存空间(默认情况下,每个线程都占 512 KB)
- 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
- 线程越多,CPU 在调用线程上的开销就越大
- 程序设计更加复杂,比如线程间的通信,多线程的数据共享
拓展:
内存五大分区
堆区,栈区,未初始化常量区,初始化常量区,代码区
临时变量存放在栈区
多线程原理
cpu 在单位时间片里快速在各个线程之间切换
多线程生命周期
- 新建 start -> Runnable 就绪 -> 等待CPU调度当前线程 -> 运行 Running
- 就绪 Runnable
- 运行 Running/等待CPU调度当前线程
- 堵塞 blocked/调用sleep方法、等待同步锁、从可调度线程池移除
- 死亡 任务执行完成、强制退出
#线程池的原理
通过"线程池大小小于核心线程池大小"判断线程池是否已满
-
如果未达到饱和 -> 创建线程执行任务
-
如果已达到饱和 -> 线程池判断工作队列已经满
1)未满 -> 将任务push进队列
2)满了
-
a. 且 maximumPoolSize > corePoolSize, 将创建新的线程来执行任务
-
b. 交给饱和策略去处理
a) Abort 策略:默认策略,新任务提交时直接抛出未检查的异常 RejectedExecutionException, 该异常可由调用者捕获
b)CallerRuns 策略:为调节机制,既不抛弃任务也不抛出异常,而是将某些任务回退到调用者。不会在线程池的线程中执行新的任务,而是在调用 exector 的线程中运行新的任务。
c)Discard 策略:新提交的任务被抛弃
d)DiscardOldest 策略:队列的是“队头”的任务,然后尝试提交新的任务,(不适合工作队列为优先队列场景)
-
注意:start 不代表,立马开始跑
代码模拟线程生命周期
部分线程周期方法
- 创建线程
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument
- 线程启动线程
- (void)start
- 取消线程
- (void)cancel
- 退出线程
+ (void)exit;
- 设置睡眠时间
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
sleep(unsigned int)
- 是否正在执行
@property (readonly, getter=isExecuting) BOOL executing
- 是否已结束
@property (readonly, getter=isFinished) BOOL finished
- 是否已取消
@property (readonly, getter=isCancelled) BOOL cancelled
线程通讯
线程通讯一般是指,多线程之间进行传值通讯
**NSPort:**基于端的一些通讯,端与端之间的通讯
代码实现线程通讯功能
PortViewController.m
#import "PortViewController.h"
#import <objc/runtime.h>
#import "KCPerson.h"
@interface PortViewController ()<NSMachPortDelegate>
@property (nonatomic, strong) NSPort *myPort;
@property (nonatomic, strong) KCPerson *person;
@end
@implementation PortViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"Port线程通讯";
self.view.backgroundColor = [UIColor whiteColor];
//1. 创建主线程的port
// 子线程通过此端口发送消息给主线程
self.myPort = [NSMachPort port];
//2. 设置port的代理回调对象
self.myPort.delegate = self;
//3. 把port加入runloop,接收port消息
[[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode];
self.person = [[KCPerson alloc] init];
[NSThread detachNewThreadSelector:@selector(personLaunchThreadWithPort:)
toTarget:self.person
withObject:self.myPort];
}
#pragma mark - NSMachPortDelegate
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"VC回调回来了 == %@",[NSThread currentThread]);
}
KCPerson.h
@interface KCPerson : NSObject
- (void)personLaunchThreadWithPort:(NSPort *)port;
@end
KCPerson.m
#import "KCPerson.h"
@interface KCPerson()<NSMachPortDelegate>
@property (nonatomic, strong) NSPort *vcPort;
@property (nonatomic, strong) NSPort *myPort;
@end
@implementation KCPerson
- (void)personLaunchThreadWithPort:(NSPort *)port{
NSLog(@"VC 响应了Person里面");
@autoreleasepool {
//1. 保存主线程传入的port
self.vcPort = port;
//2. 设置子线程名字
[[NSThread currentThread] setName:@"KCPersonThread"];
//3. 开启runloop
[[NSRunLoop currentRunLoop] run];
//4. 创建自己port
self.myPort = [NSMachPort port];
//5. 设置port的代理回调对象
self.myPort.delegate = self;
//6. 完成向主线程port发送消息
[self sendPortMessage];
}
}
/**
* 完成向主线程发送port消息
*/
- (void)sendPortMessage {
NSData *data1 = [@"ty" dataUsingEncoding:NSUTF8StringEncoding];
NSMutableArray *array =[[NSMutableArray alloc]initWithArray:@[data1,self.myPort]];
// 发送消息到VC的主线程
// 第一个参数:发送时间。
// msgid 消息标识。
// components,发送消息附带参数。
// reserved:为头部预留的字节数
[self.vcPort sendBeforeDate:[NSDate date]
msgid:10086
components:array
from:self.myPort
reserved:0];
}
打印结果:
以上代码主要是完成了这样一个流程 :
PortViewController -> KCPerson -> PortViewController
- 就是 PortViewController 在子线程里调用了 KCPerson 的实例方法,并且传了一个 NSPort 过去
- KCPerson 接受到这个 vcPort 之后存起来,自己又创建了一个 myPort
- KCPerson 用 vcPort 这个端口又给 PortViewController 发送了一条消息,并带来一些参数,比如 myPort。
- 在 PortViewController 里的 NSMachPortDelegate 代理就被调用了,message 里面就是从 KCPerson 传过来的信息。
这样就完成了基于端与端之间的通讯,并且是指主线程和子线程之间完成的。
然后我们看到NSPort 的代理 NSMachPortDelegate 方法
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"VC回调回来了 == %@",[NSThread currentThread]);
}
回调有个参数 NSPortMessage,查看一下里面都有哪些参数
PortViewController.m
#pragma mark - NSMachPortDelegate
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"VC回调回来了 == %@",[NSThread currentThread]);
[self getAllProperties:message];
}
- (void)getAllProperties:(id)somebody{
u_int count = 0;
objc_property_t *properties = class_copyPropertyList([somebody class], &count);
for (int i = 0; i < count; i++) {
const char *propertyName = property_getName(properties[i]);
NSLog(@"%@",[NSString stringWithUTF8String:propertyName]);
}
}
打印结果:
知道了带有哪些参数,那我们就读取打印一下之前回传时的数据
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"VC回调回来了 == %@",[NSThread currentThread]);
NSLog(@"从person 传过来一些信息:");\
NSLog(@"receivePort == %@",[message valueForKey:@"receivePort"]);
NSLog(@"sendPort == %@",[message valueForKey:@"sendPort"]);
NSLog(@"msgid == %@",[message valueForKey:@"msgid"]);
NSLog(@"components == %@",[message valueForKey:@"components"]);
打印结果:
刚才完成的是 PortViewController 跟 KCPerson 通讯,然后 KCPerson 使用 NSPort 又向 PortViewController 发消息的流程,
接下来在添加一个流程,刚才 PortViewController 有收到 KCPerson 向它发送的消息,那么现在 PortViewController 接受到消息之后,再给 KCPerson 发一个消息
代码实现:
PortViewController.m
#import "PortViewController.h"
#import <objc/runtime.h>
#import "KCPerson.h"
@interface PortViewController ()<NSMachPortDelegate>
@property (nonatomic, strong) NSPort *myPort;
@property (nonatomic, strong) KCPerson *person;
@end
@implementation PortViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"Port线程通讯";
self.view.backgroundColor = [UIColor whiteColor];
NSLog(@"PortViewController 新建子线程,去调用 KCPerson 主线程的方法\n");
//1. 创建主线程的port
// 子线程通过此端口发送消息给主线程
self.myPort = [NSMachPort port];
//2. 设置port的代理回调对象
self.myPort.delegate = self;
//3. 把port加入runloop,接收port消息
[[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode];
self.person = [[KCPerson alloc] init];
[NSThread detachNewThreadSelector:@selector(personLaunchThreadWithPort:)
toTarget:self.person
withObject:self.myPort];
}
#pragma mark - NSMachPortDelegate
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"收到 KCPerson 发的消息后, PortViewController 又给 KCPerson 发送消息\n");
NSData *data2 = [@"Janice" dataUsingEncoding:NSUTF8StringEncoding];
//此 sendPort 是在 KCPerson 里新建的那个 port,所以在使用它发送消息时,需要加入到 NSRunLoop 中
NSPort *sendPort = [message valueForKey:@"sendPort"];
NSMutableArray *array = [[NSMutableArray alloc] initWithArray:@[data2, self.myPort]];
[[NSRunLoop currentRunLoop] addPort:sendPort forMode:NSDefaultRunLoopMode];
// 发送消息KCPerson的主线程
// 第一个参数:发送时间。
// msgid 消息标识。
// components,发送消息附带参数。
// reserved:为头部预留的字节数
BOOL ruselt = [sendPort sendBeforeDate:[NSDate date]
msgid:10000
components:array
from:self.myPort
reserved:0];
}
KCPerson.h
#import <Foundation/Foundation.h>
@interface KCPerson : NSObject
- (void)personLaunchThreadWithPort:(NSPort *)port;
@end
KCPerson.m
#import "KCPerson.h"
@interface KCPerson()<NSMachPortDelegate>
@property (nonatomic, strong) NSPort *vcPort;
@property (nonatomic, strong) NSPort *myPort;
@end
@implementation KCPerson
- (void)personLaunchThreadWithPort:(NSPort *)port{
NSLog(@"KCPerson 里的方法在 PortViewController 里通过子线程被调用了\n");
@autoreleasepool {
//1. 保存主线程传入的port
self.vcPort = port;
//2. 设置子线程名字
[[NSThread currentThread] setName:@"KCPersonThread"];
//3. 开启runloop
[[NSRunLoop currentRunLoop] run];
//4. 创建自己port
self.myPort = [NSMachPort port];
//5. 设置port的代理回调对象
self.myPort.delegate = self;
//6. 完成向主线程port发送消息
[self sendPortMessage];
}
}
/**
* 完成向主线程发送port消息
*/
- (void)sendPortMessage {
NSLog(@"KCPerson 给 PortViewController 发送消息\n");
NSData *data1 = [@"ty" dataUsingEncoding:NSUTF8StringEncoding];
NSData *data2 = [@"Janice" dataUsingEncoding:NSUTF8StringEncoding];
NSMutableArray *array =[[NSMutableArray alloc]initWithArray:@[data1,self.myPort]];
// 发送消息到VC的主线程
// 第一个参数:发送时间。
// msgid 消息标识。
// components,发送消息附带参数。
// reserved:为头部预留的字节数
[self.vcPort sendBeforeDate:[NSDate date]
msgid:10086
components:array
from:self.myPort
reserved:0];
}
#pragma mark - NSMachPortDelegate
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"KCPerson 收到从 PortViewController 传过来一些信息:\n");
NSLog(@"components == %@",[message valueForKey:@"components"]);
NSLog(@"receivePort == %@",[message valueForKey:@"receivePort"]);
NSLog(@"sendPort == %@",[message valueForKey:@"sendPort"]);
NSLog(@"msgid == %@",[message valueForKey:@"msgid"]);
}
打印结果:
可以将打印结果对比着代码看,就一目了然
拓展:C 与 OC 的桥接
- _bridge 只做类型转换,但是不修改对象(内存)管理权;
- _bridge_retained(也可以使用 CFBridgingRetain)将Objective-C 的对象转换为 Core Foundation 的对象,同时将对象(内存)的管理权交给我们,后续需要使用CFRelease 或者相关方法来释放对象;
- _bridge_transfer(也可以使用 CFBridgingRelease)将 Core Foundation 的对象转换为 Objective-C 的对象,同时将对象(内存)的管理权交给ARC。