iOS多线程 基础

iOS 多线程

1.线程与进程

1.1 线程的定义

  1. 线程是进程的基本执行单元,一个进程的所有任务大偶在线程中执行
  2. 进程要想执行任务,必须得有线程,进程至少要有一条线程
  3. 程序启动会默认开启一条线程,这条线程被称为主线程或UI线程

1.2进程

  1. 进程是系统进行资源和调度的基本单位
  2. 在移动端进程是指在系统中正在运行的一个应用程序(注:iOS是单进程,Android可以实现多进程)
  3. 每个进程之间是独立的,每个进程均运行在其专用的切受保护的内存

1.3进程与线程的关系

  1. 地址空间:统一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
  2. 资源拥有:同一进程内的线程共享本进程的资源内存、I/O、CPU等,但是进程之间的资源是独立的。
  3. 相互影响:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃后整个进程就死掉了,所以多进程要比多线程健壮
  4. 资源占用:进程切换时资源消耗大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
  5. 执行过程:每个独立的进程有一个程序的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须存在应用程序中(进程),有应用程序提供多个线程执行控制(多线程开发)。
  6. 线程是处理器调度的基本单位,但进程不是,线程也是进程执行的基本单位

1.4线程与队列的关系

多线程中的队列有:串行队列,并发队列,全局队列,主队列。
队可以理解为一种数据结构,以某种方式,等待线程去执行。

1.5 iOS线程与Runloop的关系

  1. 线程与Runloop是一一对应的,一个Runloop对应一个核心线程,为什么说是核心线程,因为Runloop是可以嵌套的,但是核心只有一个,他们的对应关系保存在一个全局字典里
  2. Runloop是用来管理线程的,线程执行完任务时会进入休眠状态,有任务进来时会被唤醒,开始执行任务。所以说Runloop是事件驱动的。
  3. Runloop在第一次获取时被创建,线程结束时被销毁。主线程的Runloop在程序启动的时候就会被创建。
  4. 子线程的Runloop是懒加载的,只有在使用的时候才被创建。
    注:在子线程使用NSTimer时要注意确保子线程的Runloop已经创建,否则NSTimer不会生效。

2.多线程

2.1 多线程的定义

多线程是一个进程中并发多个线程同时执行各自的任务。就是由单核CPU通过时间片不断的切换执行程序。

分时操作系统会把CPU的时间划分为长短基本相同的时间区间(时间片),在一个时间片内,CPU只能处理一个线程中的一个任务,对于一个单核CPU来说,在不同的时间片来执行不同线程中的任务,就形成了多个任务在同时执行的“假象”。

2.2多线程的优缺点

1.优点
    *减少应用程序的堵塞,增加程序的执行效率
    *适当提高CPU和内存的利用率
    *线程上的任务执行完成后,线程自动销毁(部分API可实现)
3.缺点
    *线程的开启需要占用一定的内存空间,默认是512KB/线程
    *线程开启的越多内存占用越大,会降低程序的性能
    *线程越多CPU在调用线程上的开销就越大
    *程序设计更加复杂,需要考虑线程间通信,多线程的数据共享等问题

2.3 多线程的生命周期

线程的生命周期.jpg
可能造成阻塞的条件:
锁(lock),循环引用,sleep()函数,循环执行

2.4 可调度线程池

什么是线程池:

提供一组线程资源用来复用线程资源的一个池子

线程池中常用参数释义:

  • corePoolSize 线程池的基本大小(核心线程池大小)
  • maximumPool 线程池最大大小
  • keepAliveTime 线程池中超过corePoolSize数目的空闲线程的最大存活时间
  • unit keepAliveTime参数的时间单位
  • workQueue 任务阻塞队列
  • threadFactory 新建线程的工厂
  • handler 当提交任务数超过maximumPoolSizeworkQueue之和时,任务会交给RejectedExecutionHandler来处理

线程池调度原理:

线程池调度原理.jpg

饱和策略:

  1. AboutPolicy 直接抛出RejectedExecutionExeception异常,阻止系统的正常运行
  2. CallerRunsPolicy 将任务回退到调用者
  3. DisOldestPolicy 丢掉等待最久的任务
  4. DisCardPolicy 直接丢弃任务

注: 以上四种拒绝策略均实现的RejectedExencutionHandler

注:iOS开发中不会直接接触到线程池,这是因为GCD已经包含了线程池的管理,我们常用的就是GCD(NSOperation底层也是GCD),所以只需要通过GCD获取线程来执行任务即可。

继续探索多线程更底层的知识可以尝试搜索Java Posix

2.6 iOS的几种多线程技术方案

iOS多线程API.jpg
  1. pthread:即POSIX Thread,是线程的POSIX标准,是一套通用的多线程API,可以在Unix/Linux/Windows等平台跨平台使用。iOS中基本不使用。
  2. NSThread:苹果封装的面向对象的线程类,可以直接操作线程,比起GCDNSThread效率更高,由程序员自行创建,当线程中的任务执行完毕后,线程会自动退出,程序员也可手动管理线程的生命周期。使用频率较低。
  3. GCD:全称Grand Central Dispatch,由C语言实现,是苹果为多核的并行运算提出的解决方案,GCD会自动利用更多的CPU内核,自动管理线程的声明周期,程序员只需要告诉GCD需要执行的任务,无需编写任何管理线程的代码。GCD也是iOS使用频率最高的多线程技术。
  4. NSOperation:基于GCD封装的面向对象的多线程类,相较于GCD提供了很多方便的API,使用频率较高。

3. 线程间的通讯

3.1 几种线程间的通讯方式

通过上面的介绍我们对线程有了基本的了解,那么线程之间是如何通讯的呢?其实我们都知道的就是在子线程获取数据,然后切换到主线程进行刷新UI,其实这只是表象,并不是真正的线程通讯方式,如果在面试中这样回答基本就是回家等消息了。那么线程之间倒地是如何通讯的呢?在苹果官方文档中给我们列出了线程间通讯的几种方式:

线程间通讯的几种方式.jpg

线程之间有许多通信方式,每种方式都有其优缺点。配置线程本地存储列出了您可以在OS x中使用的最常见的通信机制(除了消息队列和Cocoa分布式对象,这些技术在iOS中也可用)。这个表中的技术是按照复杂度递增的顺序列出的。其中后两种只能在OS X中使用。

  • Direct messaging:这个就是我们常用的selector系列,例如-performSelector:
  • Global variables, shared memory, and objects: 直接通过全局变量、共享内存等方式,但这种方式会造成资源抢夺,涉及到线程安全问题。
  • Conditions:条件锁,一种特殊的锁,我们可以使用它来控制线程何时执行特定部分的代码。
  • Run loop sources: 自定义Runloop来设置用于在线程上接收应用程序特定消息的循环源,因为它们是事件驱动的,所以运行循环源会在无事可做时将线程自动休眠,从而提高线程的效率。
  • Ports and sockets: 通过端口和套接字来实现线程间通讯。

4. iOS中使用线程间通讯示例(使用NSPort)

此处的例子是通过一个PortPerson对象跟ViewController进行发消息的实例。

代码中首先将VCself.myPort添加到主线程的Runloop中,然后起新线程让PersonVC发送消息,VC在收到消息后也像Person发送一条消息,在此过程中并没有切换线程的代码,通过打印线程可以看到VC的操作是在主线程中完成的,Person是在子线程(7)中完成的,通过这些代码就完成了线程间的通讯。

PortPerson 代码:

#import 

NS_ASSUME_NONNULL_BEGIN

@interface PortPerson : NSObject
- (void)personLaunchThreadWithPort:(NSPort *)port;
@end

NS_ASSUME_NONNULL_END

#import "PortPerson.h"

@interface PortPerson()

@property (nonatomic, strong) NSPort *vcPort;
@property (nonatomic, strong) NSPort *myPort;

@end

@implementation PortPerson

- (void)personLaunchThreadWithPort:(NSPort *)port {
    
    @autoreleasepool {
        NSLog(@"Person Thread %@", [NSThread currentThread]);
        //1. 保存主线程传入的port
        self.vcPort = port;
        //2. 设置子线程名字
        [[NSThread currentThread] setName:@"PortPersonThread"];
        //3. 开启runloop
        [[NSRunLoop currentRunLoop] run];
        //4. 创建自己port
        self.myPort = [NSMachPort port];
        //5. 设置port的代理回调对象
        self.myPort.delegate = self;
        //6. 完成向主线程port发送消息
        [self sendPortMessage];
    }
    
}

- (void)sendPortMessage {
 
    NSData *data1 = [@"这是一条port信息" dataUsingEncoding:NSUTF8StringEncoding];
//    NSData *data2 = [@"data2" 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 == %@",[(id)message valueForKey:@"components"]);
    NSLog(@"receivePort == %@",[(id)message valueForKey:@"receivePort"]);
    NSLog(@"sendPort == %@",[(id)message valueForKey:@"sendPort"]);
    NSLog(@"msgid == %@",[(id)message valueForKey:@"msgid"]);
}

@end

PortViewController 代码:

#import "PortViewController.h"
#import 
#import "PortPerson.h"

@interface PortViewController ()

@property (nonatomic, strong) NSPort *myPort;
@property (nonatomic, strong) PortPerson *person;

@end

@implementation PortViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor whiteColor];
    // 创建主线程的port,子线程通过此端口发送消息给主线程
    self.myPort = [NSMachPort port];
    // 设置port的代理回调对象
    self.myPort.delegate = self;
    // 把port加入runloop,接收port消息
    [[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode];
    
    self.person = [[PortPerson alloc] init];
    
    [NSThread detachNewThreadSelector:@selector(personLaunchThreadWithPort:)
                             toTarget:self.person
                           withObject:self.myPort];
}

- (void)handlePortMessage:(NSPortMessage *)message {
    
    NSLog(@"VC Thread --- %@", [NSThread currentThread]);
    
    NSLog(@"从 person 传过来一些信息");
    
    NSArray *messageArr = [(id)message valueForKey:@"components"];
    
    NSString * dataStr = [[NSString alloc] initWithData:messageArr.firstObject encoding:NSUTF8StringEncoding];
    
    NSLog(@"通过port传过来一些信息:%@", dataStr);

    NSPort *destinPort = [(id)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);
}

@end

打印结果:

打印结果.jpg

注意!!!

  1. NSPort对象必须添加到要接收消息的线程的Runloop中。
  2. 接收消息的对象实现NSPortDelegate协议的-handlePortMessage:方法来获取消息内容。!

你可能感兴趣的:(iOS多线程 基础)