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。
相關文章
相關標籤/搜索