进程和线程
1.1 进程
进程是指在系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存中。
1.2 线程
线程是进程的基本执行单元,进程中的所有任务都在线程中执行,因此,一个进程中至少要有一个线程。iOS
程序启动后会默认开启一个主线程,也叫UI
线程。
1.3 进程与线程的关系
- 地址空间:同一进程中的地址空间可以被本进程中的多个线程共享,但进程与进程之间的地址空间是独立的
- 资源拥有:同一进程中的资源可以被本进程中的所有线程共享,如
内存
、I/O
、CUP
等等,但进程与进程之间的资源是相互独立的 - 一个进程中的任一线程崩溃后,都会导致整个进程崩溃,但进程奔溃后不会影响另一个进程
- 进程可以看做是线程的容器,每个进程都有一个程序运行的入口,但线程不能独立运行,必须依存于进程
1.4 线程与Runloop
的关系
线程
与Runloop
是一一对应的,一个Runloop
对应一个核心线程
,为什么说是核心,因为Runloop
是可以嵌套的,但核心的只有一个,他们的对应关系保存在一个全局字典里Runloop
是来管理线程的,线程执行完任务时会进入休眠状态,有任务进来时会被唤醒开始执行任务(事件驱动)Runloop
在第一次获取时被创建,线程结束时被销毁- 主线程的
Runloop
在程序启动时就默认创建好了 - 子线程的
Runloop
是懒加载的,只有在使用时才被创建,因此在子线程中使用NSTimer
时要注意确保子线程的Runloop
已创建,否则NSTimer
不会生效。
1.5 线程的生命周期
线程的生命周期可分为五个阶段:新建
、就绪
、调用
、阻塞
、死亡
。
多线程
2.1概念和原理
一个进程中可以并发多个线程同时执行各自的任务,叫做多线程。分时操作系统会把CPU的时间划分为长短基本相同的时间区间,叫“时间片”,在一个时间片内,CPU只能处理一个线程中的一个任务,对于一个单核CPU来说,在不同的时间片来执行不同线程中的任务,就形成了多个任务在同时执行的“假象”:
上图中,CPU在时间片t3开始执行线程3中的任务,但任务还没执行完,来到了t4,开始执行线程4中的任务,在t4这个时间片内就执行完了线程4的任务,到t5时接着执行线程3的任务。 现在都是多核CPU,每个核心都可以单独处理任务,实现“真正”的多线程,但是一个App动辄几十个并发线程,那么每个核心仍然以上述原理实现多线程。2.2 ios中常用的多线程
在iOS中,有下列几种多线程的使用方式:
1.pthread
:即POSIX Thread
,缩写称为Pthread
,是线程的POSIX
标准,是一套通用的多线程API
,可以在Unix/Linux/Window
等平台跨平台使用。iOS
中基本不使用。
2.NSThread
:苹果封装的面向对象的线程类,可以直接操作线程,比起GCD
,NSThread
效率更高,由程序员自行创建,当线程中的任务执行完毕后,线程会自动退出,程序员也可手动管理线程的生命周期。使用频率较低。
3.GCD
:全称Grand Central Dispatch
,由C语言
实现,是苹果为多核
的并行运算提出的解决方案,CGD
会自动利用更多的CPU内核
,自动
管理线程的生命周期,程序员只需要告诉GCD
需要执行的任务,无需编写任何管理线程的代码。GCD
也是iOS
使用频率最高的多线程技术
。
4.NSOperation
:基于GCD
封装的面向对象的多线程技术,常配合NSOperationQueue
使用,使用频率较高。
线程池
线程池(Thread Pool)
顾名思义就是一个管理多个线程生命周期的池子。iOS开发中不会直接接触到线程池,这是因为GCD已经包含了线程池的管理,我们只需要通过GCD获取线程来执行任务即可。线程的生命周期
一个线程的生命周期包括创建
--就绪
--运行
--死亡
这四个阶段,我们可以通过阻塞、退出等来控制线程的生命周期。
线程间的通讯
4.1 几种线程间的通讯方式
上图的表格是按照技术复杂度由低到高顺序排列的,其中后两种只能在OS X中使用。Direct messaging
:这是大家非常熟悉的-performSelector
:系列。Global variables...
:直接通过全局
变量、共享内存
等方式,但这种方式会造成资源抢夺,涉及到线程安全问题。Conditions:
一种特殊的锁--条件锁,当使用条件锁使一个线程等待(wait)时,该线程会被阻塞并进入休眠状态,在另一个线程中对同一个条件锁发送信号(single),则等待中的线程会被唤醒继续执行任务。Run loop sources
:通过自定义Run loop sources
来实现,后面的文章会单独研究Run loop
。Ports and sockets
:通过端口和套接字来实现线程间通讯。
4.2 线程间通讯的示例
- 首先在主线程中创建port,设置代理,然后把port添加到runloop中,开启子线程。
- (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];
}
- 在这里,子线程通Port发送消息给主线程。
- (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 = [@"Gavin" dataUsingEncoding:NSUTF8StringEncoding];
NSData *data2 = [@"Cooci" 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];
}
- 主线程拿到数据后,在发送消息给子线程。
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"VC == %@",[NSThread currentThread]);
NSLog(@"从person 传过来一些信息:");
// NSLog(@"localPort == %@",[message valueForKey:@"localPort"]);
// NSLog(@"remotePort == %@",[message valueForKey:@"remotePort"]);
// NSLog(@"receivePort == %@",[message valueForKey:@"receivePort"]);
// NSLog(@"sendPort == %@",[message valueForKey:@"sendPort"]);
// NSLog(@"msgid == %@",[message valueForKey:@"msgid"]);
// NSLog(@"components == %@",[message valueForKey:@"components"]);
//会报错,没有这个隐藏属性
//NSLog(@"from == %@",[message valueForKey:@"from"]);
NSArray *messageArr = [message valueForKey:@"components"];
NSString *dataStr = [[NSString alloc] initWithData:messageArr.firstObject encoding:NSUTF8StringEncoding];
NSLog(@"传过来一些信息 :%@",dataStr);
NSPort *destinPort = [message valueForKey:@"remotePort"];
if(!destinPort || ![destinPort isKindOfClass:[NSPort class]]){
NSLog(@"传过来的数据有误");
return;
}
NSData *data = [@"VC收到!!!" dataUsingEncoding:NSUTF8StringEncoding];
NSMutableArray *array =[[NSMutableArray alloc]initWithArray:@[data,self.myPort]];
// 非常重要,如果你想在Person的port接受信息,必须加入到当前主线程的runloop
[[NSRunLoop currentRunLoop] addPort:destinPort forMode:NSDefaultRunLoopMode];
NSLog(@"VC == %@",[NSThread currentThread]);
BOOL success = [destinPort sendBeforeDate:[NSDate date]
msgid:10010
components:array
from:self.myPort
reserved:0];
NSLog(@"%d",success);
}
- 子线程接收到主线程发送的消息
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"person:handlePortMessage == %@",[NSThread currentThread]);
NSLog(@"从VC 传过来一些信息:");
NSLog(@"components == %@",[message valueForKey:@"components"]);
NSLog(@"receivePort == %@",[message valueForKey:@"receivePort"]);
NSLog(@"sendPort == %@",[message valueForKey:@"sendPort"]);
NSLog(@"msgid == %@",[message valueForKey:@"msgid"]);
}
至此,线程间通过port实现通讯的例子就完成了。