美文网首页iOS-多线程
iOS多线程 基础

iOS多线程 基础

作者: just东东 | 来源:发表于2020-10-10 11:18 被阅读0次

    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 <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface PortPerson : NSObject
    - (void)personLaunchThreadWithPort:(NSPort *)port;
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "PortPerson.h"
    
    @interface PortPerson()<NSMachPortDelegate>
    
    @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 <objc/runtime.h>
    #import "PortPerson.h"
    
    @interface PortViewController ()<NSMachPortDelegate>
    
    @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多线程 基础

        本文链接:https://www.haomeiwen.com/subject/esyluktx.html