多线程是iOS开发中的一个重要的环节,不论是在平常的开发,仍是面试中,都有着举足轻重的地位,因此打算开辟一个小专题,研究多线程相关的底层原理。这一篇章是第一篇,介绍一些基础的概念性的多线程。前端
当一个程序进入内存运行时,即变成一个进程。进程是处于运行过程当中的程序,而且具备必定的独立功能,进程是系统进行资源分配和调度的一个独立单位。每一个进程之间是独立的,每一个进程均运行在其专用的且受保护的内存。程序员
在iOS
开发中,一个App在内存中就是一个进程,且相互独立,只能访问本身的沙盒控件,这也是苹果运行可以流畅安全的一个主要缘由。面试
线程的定义,主要能够归结为如下3点:后端
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。数组
资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,可是进程之间 的资源是独立的。安全
一个进程崩溃后,在保护模式下不会对其余进程产生影响,可是一个线程崩溃整个进程 都死掉。因此多进程要比多线程健壮。多线程
进程切换时,消耗的资源大,效率高。因此涉及到频繁的切换时,使用线程要好于进程。 一样若是要求同时进行而且又要共享某些变量的并发操做,只能用线程不能用进程并发
执行过程:每一个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。可是线 程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。异步
线程是处理器调度的基本单位,可是进程不是。socket
runloop
与线程是一一对应的,一个runloop
对应一个核心的线程,为何说是核心的, 是由于runloop
是能够嵌套的,可是核心的只能有一个,他们的关系保存在一个全局的字典里。runloop
是来管理线程的,当线程的runloop
被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。runloop
在第一次获取时被建立,在线程结束时被销毁。runloop
在程序一启动就默认建立好了。runloop
是懒加载的,只有当咱们使用的时候才会建立,因此在子线程用定时器要注意:确保子线程的runloop
被建立,否则定时器不会回调队列,又称为伫列(queue
),是先进先出(FIFO, First-In-First-Out
)的线性表,在具体应用中一般用链表或者数组来实现。装载线程任务的队形结构。队列只容许在后端(称为rear
)进行插入操做,在前端(称为front
)进行删除操做。队列的操做方式和堆栈相似,惟一的区别在于队列只容许新数据在后端进行添加。
队列的类型决定了任务的执行方式(并发、串行),队列包括如下几种:
Concurrent Dispatch Queue
): 线程执行能够同时一块儿进行执行,不须要上一个执行完,就能执行下一个的。Serial Dispatch Queue
): 线程执行只能依次逐一前后有序的执行,等待上一个执行完,再执行下一个。同步 sync
: 只能在当前线程按前后顺序依次执行任务,不具有开启新线程的能力。
异步 async
: 在新的线程中执行任务,具有开启新线程的能力。
一个进程中能够并发多个线程同时执行各自的任务,叫作多线程。
分时操做系统会把CPU
的时间划分为长短基本相同的时间区间,叫时间片,在一个时间片内,CPU
只能处理一个线程中的一个任务,对于一个单核CPU来讲,在不一样的时间片来执行不一样线程中的任务,就造成了多个任务在同时执行的“假象”。
多线程即为单位时间片里快速在各个线程之间切换
start
以后就会runnable
,而后等待cpu
分配资源,进行调用cpu
调度以后就会到running
状态sleep
,或者死锁等操做以后就会形成线程阻塞.当阻塞解除以后不会直接到running
状态而是又到就绪状态优势:
缺点:
pthread
:即POSIX Thread
,缩写称为pthread
,是线程的POSIX
标准,是一套通用的多线程API
,能够在Unix/Linux/Windows
等平台跨平台使用。iOS中基本不使用。NSThread
:苹果封装的面向对象的线程类,能够直接操做线程,比起GCD
,NSThread
效率更高,由程序员自行建立,当线程中的任务执行完毕后,线程会自动退出,程序员也可手动管理线程的生命周期。使用频率较低。GCD
:全称Grand Central Dispatch
,由C语言实现,是苹果为多核的并行运算提出的解决方案,CGD
会自动利用更多的CPU
内核,自动管理线程的生命周期,程序员只须要告诉GCD
须要执行的任务,无需编写任何管理线程的代码。GCD
也是iOS使用频率最高的多线程技术。NSOperation
:基于GCD
封装的面向对象的多线程技术,常配合NSOperationQueue
使用,使用频率较高。
GCD
仅仅支持FIFO
队列,不支持异步操做之间的依赖关系设置。而NSOperation
中的队列能够被从新设置优先级,从而实现不一样操做的执行顺序调整。NSOperation
支持KVO
,能够观察任务的执行状态。GCD
更接近底层,GCD
在追求性能的底层操做来讲,是速度最快的。GCD
须要本身写更多的代码来实现,而NSOperation
已经内建了这些支持NSOperation
更好。底层代码中,任务之间不太互相依赖,而须要更高的并发能力,GCD
则更有优点线程池是多线程处理的一种形式,处理过程当中将任务添加到队列,而后在建立线程后自动启动这些任务。线程池中的线程都是后台线程。每一个线程都有默认的堆栈大小,以默认的优先级运行,并处在多线程单元中。
AbortPolicy
:默认策略。直接抛出RejectedExecutionExeception
异常阻止系统正常运行,该异常由调用者捕获CallerRunsPolicy
:调节机制。既不抛弃也不报异常。将任务回退给调用者DisOldestPolicy
:丢掉等待最久的任务DisCardPolicy
:直接丢弃任务苹果的官方文档中,给出了几种线程间通讯的方式:
performSelector
的一系列方法,能够实现由某一线程指定在另外的线程上执行任务。由于任务的执行上下文是目标线程,这种方式发送的消息将会自动的被序列化。Runloop sources
: 一个自定义的 Runloop source
配置可让一个线程上收到特定的应用程序消息。因为 Runloop source
是事件驱动的,所以在无事可作时,线程会自动进入睡眠状态,从而提升了线程的效率。Ports and sockets
: 基于端口的通讯是在两个线程之间进行通讯的一种更为复杂的方法,但它也是一种很是可靠的技术。更重要的是,端口和套接字可用于与外部实体(例如其余进程和服务)进行通讯。为了提升效率,使用 Runloop source
来实现端口,所以当端口上没有数据等待时,线程将进入睡眠状态。Cocoa
分布式对象: 分布式对象是一种 Cocoa 技术,可提供基于端口的通讯的高级实现。尽管能够将这种技术用于线程间通讯,可是强烈建议不要这样作,由于它会产生大量开销。分布式对象更适合与其余进程进行通讯,尽管在这些进程之间进行事务的开销也很高对于performSelector
的通讯,平时在开发中运用的比较多,就不详细讲述了,咱们讲一个线程以前,运用端口来传递消息的例子来加深印象。
首先咱们建立一个类来发送消息:
@interface WYPerson : NSObject
- (void)personLaunchThreadWithPort:(NSPort *)port;
@end
复制代码
#import "WYPerson.h"
@interface WYPerson()<NSMachPortDelegate>
@property (nonatomic, strong) NSPort *vcPort;
@property (nonatomic, strong) NSPort *myPort;
@end
@implementation WYPerson
- (void)personLaunchThreadWithPort:(NSPort *)port{
NSLog(@"VC 响应了Person里面");
@autoreleasepool {
✅//1. 保存主线程传入的port
self.vcPort = port;
✅//2. 设置子线程名字
[[NSThread currentThread] setName:@"WYPersonThread"];
❗️//3. 开启runloop(重点在这)
[[NSRunLoop currentRunLoop] run];
✅//4. 建立本身port
self.myPort = [NSMachPort port];
✅//5. 设置port的代理回调对象(NSMachPortDelegate)
self.myPort.delegate = self;
✅//6. 完成向主线程port发送消息
[self sendPortMessage];
}
}
/** * 完成向主线程发送port消息 */
- (void)sendPortMessage {
NSData *data1 = [@"WY" 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(@"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"]);
}
复制代码
接着咱们建立PortViewController
用来接收消息和回调消息给发送者
#import "PortViewController.h"
#import <objc/runtime.h>
#import "WYPerson.h"
@interface PortViewController ()<NSMachPortDelegate>
@property (nonatomic, strong) NSPort *myPort;
@property (nonatomic, strong) WYPerson *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消息(此时主线程已经再跑,不须要run)
[[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode];
self.person = [[WYPerson alloc] init];
[NSThread detachNewThreadSelector:@selector(personLaunchThreadWithPort:)
toTarget:self.person
withObject:self.myPort];
}
#pragma mark - NSMachPortDelegate
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"VC == %@",[NSThread currentThread]);
NSLog(@"从person 传过来一些信息:");
//会报错,没有这个隐藏属性
//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)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]);
}
}
复制代码
打印结果以下:
经过上面的例子,咱们已经实现了线程之间的通讯
稍微总结一下NSPort
的使用要点:
NSPort
对象必须添加到要接收消息的线程的Runloop
中,必须由Runloop
来进行管理NSPortDelegate
协议的-handlePortMessage:
方法来获取消息内容