OC底层原理(15)-- 多线程—(NSThread基本使用、NSPort 通信)

线程定义 (雇员)

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

进程定义(奶茶店)

  1. 进程是指在系统中正在运行的一个应用程序
  2. 每一个进程之间是独立的,每一个进程均运行在其专用的且受保护的内存

线程和进程的关系与区别

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

**扩展:**苹果是单进程,沙盒资源会更安全,切换进程消耗资源会特别大,因此采用单进程体验更流畅安全

多线程的意义(收银员的意义)

优势

  1. 能适当提升程序的执行效率
  2. 能适当提升资源的利用率(CPU,内存)
  3. 线程上的任务执行完成后,线程会自动销户

缺点

  1. 开启线程须要占用必定的内存空间(默认状况下,每一个线程都占 512 KB)
  2. 若是开启大量的线程,会占用大量的内存空间,下降程序的性能
  3. 线程越多,CPU 在调用线程上的开销就越大
  4. 程序设计更加复杂,好比线程间的通讯,多线程的数据共享

拓展:bash

内存五大分区markdown

堆区,栈区,未初始化常量区,初始化常量区,代码区多线程

临时变量存放在栈区并发

多线程原理

cpu 在单位时间片里快速在各个线程之间切换oop

多线程生命周期

  • 新建 start -> Runnable 就绪 -> 等待CPU调度当前线程 -> 运行 Running
  • 就绪 Runnable
  • 运行 Running/等待CPU调度当前线程
  • 堵塞 blocked/调用sleep方法、等待同步锁、从可调度线程池移除
  • 死亡 任务执行完成、强制退出

#线程池的原理性能

经过"线程池大小小于核心线程池大小"判断线程池是否已满atom

  1. 若是未达到饱和 -> 建立线程执行任务spa

  2. 若是已达到饱和 -> 线程池判断工做队列已经满线程

    1)未满 -> 将任务push进队列

    2)满了

    • a. 且 maximumPoolSize > corePoolSize, 将建立新的线程来执行任务

    • b. 交给饱和策略去处理

      a) Abort 策略:默认策略,新任务提交时直接抛出未检查的异常 RejectedExecutionException, 该异常可由调用者捕获

      b)CallerRuns 策略:为调节机制,既不抛弃任务也不抛出异常,而是将某些任务回退到调用者。不会在线程池的线程中执行新的任务,而是在调用 exector 的线程中运行新的任务。

      c)Discard 策略:新提交的任务被抛弃

      d)DiscardOldest 策略:队列的是“队头”的任务,而后尝试提交新的任务,(不适合工做队列为优先队列场景)

注意:start 不表明,立马开始跑

代码模拟线程生命周期

部分线程周期方法

  1. 建立线程

- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument

  1. 线程启动线程

- (void)start

  1. 取消线程

- (void)cancel

  1. 退出线程

+ (void)exit;

  1. 设置睡眠时间

+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

sleep(unsigned int)

  1. 是否正在执行

@property (readonly, getter=isExecuting) BOOL executing

  1. 是否已结束

@property (readonly, getter=isFinished) BOOL finished

  1. 是否已取消

@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

  1. 就是 PortViewController 在子线程里调用了 KCPerson 的实例方法,而且传了一个 NSPort 过去
  2. KCPerson 接受到这个 vcPort 以后存起来,本身又建立了一个 myPort
  3. KCPerson 用 vcPort 这个端口又给 PortViewController 发送了一条消息,并带来一些参数,好比 myPort。
  4. 在 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 的桥接

  1. _bridge 只作类型转换,可是不修改对象(内存)管理权;
  2. _bridge_retained(也可使用 CFBridgingRetain)将Objective-C 的对象转换为 Core Foundation 的对象,同时将对象(内存)的管理权交给咱们,后续须要使用CFRelease 或者相关方法来释放对象;
  3. _bridge_transfer(也可使用 CFBridgingRelease)将 Core Foundation 的对象转换为 Objective-C 的对象,同时将对象(内存)的管理权交给ARC。
相关文章
相关标签/搜索