iOS多線程 基礎

iOS 多線程

1.線程與進程

1.1 線程的定義

  1. 線程是進程的基本執(zhí)行單元,一個進程的所有任務大偶在線程中執(zhí)行
  2. 進程要想執(zhí)行任務,必須得有線程,進程至少要有一條線程
  3. 程序啟動會默認開啟一條線程,這條線程被稱為主線程或UI線程

1.2進程

  1. 進程是系統(tǒng)進行資源和調度的基本單位
  2. 在移動端進程是指在系統(tǒng)中正在運行的一個應用程序(注:iOS是單進程,Android可以實現(xiàn)多進程)
  3. 每個進程之間是獨立的,每個進程均運行在其專用的切受保護的內存

1.3進程與線程的關系

  1. 地址空間:統(tǒng)一進程的線程共享本進程的地址空間,而進程之間則是獨立的地址空間。
  2. 資源擁有:同一進程內的線程共享本進程的資源內存、I/O、CPU等,但是進程之間的資源是獨立的。
  3. 相互影響:一個進程崩潰后,在保護模式下不會對其他進程產生影響,但是一個線程崩潰后整個進程就死掉了,所以多進程要比多線程健壯
  4. 資源占用:進程切換時資源消耗大,效率高。所以涉及到頻繁的切換時,使用線程要好于進程。同樣如果要求同時進行并且又要共享某些變量的并發(fā)操作,只能用線程,不能用進程。
  5. 執(zhí)行過程:每個獨立的進程有一個程序的入口、順序執(zhí)行序列和程序入口。但是線程不能獨立執(zhí)行,必須存在應用程序中(進程),有應用程序提供多個線程執(zhí)行控制(多線程開發(fā))。
  6. 線程是處理器調度的基本單位,但進程不是,線程也是進程執(zhí)行的基本單位

1.4線程與隊列的關系

多線程中的隊列有:串行隊列,并發(fā)隊列,全局隊列,主隊列。
隊可以理解為一種數(shù)據結構,以某種方式,等待線程去執(zhí)行。

1.5 iOS線程與Runloop的關系

  1. 線程與Runloop是一一對應的,一個Runloop對應一個核心線程,為什么說是核心線程,因為Runloop是可以嵌套的,但是核心只有一個,他們的對應關系保存在一個全局字典里
  2. Runloop是用來管理線程的,線程執(zhí)行完任務時會進入休眠狀態(tài),有任務進來時會被喚醒,開始執(zhí)行任務。所以說Runloop是事件驅動的。
  3. Runloop在第一次獲取時被創(chuàng)建,線程結束時被銷毀。主線程的Runloop在程序啟動的時候就會被創(chuàng)建。
  4. 子線程的Runloop是懶加載的,只有在使用的時候才被創(chuàng)建。
    注:在子線程使用NSTimer時要注意確保子線程的Runloop已經創(chuàng)建,否則NSTimer不會生效。

2.多線程

2.1 多線程的定義

多線程是一個進程中并發(fā)多個線程同時執(zhí)行各自的任務。就是由單核CPU通過時間片不斷的切換執(zhí)行程序。

分時操作系統(tǒng)會把CPU的時間劃分為長短基本相同的時間區(qū)間(時間片),在一個時間片內,CPU只能處理一個線程中的一個任務,對于一個單核CPU來說,在不同的時間片來執(zhí)行不同線程中的任務,就形成了多個任務在同時執(zhí)行的“假象”。

2.2多線程的優(yōu)缺點

1.優(yōu)點
    *減少應用程序的堵塞,增加程序的執(zhí)行效率
    *適當提高CPU和內存的利用率
    *線程上的任務執(zhí)行完成后,線程自動銷毀(部分API可實現(xiàn))
3.缺點
    *線程的開啟需要占用一定的內存空間,默認是512KB/線程
    *線程開啟的越多內存占用越大,會降低程序的性能
    *線程越多CPU在調用線程上的開銷就越大
    *程序設計更加復雜,需要考慮線程間通信,多線程的數(shù)據共享等問題

2.3 多線程的生命周期

線程的生命周期.jpg
可能造成阻塞的條件:
鎖(lock),循環(huán)引用,sleep()函數(shù),循環(huán)執(zhí)行

2.4 可調度線程池

什么是線程池:

提供一組線程資源用來復用線程資源的一個池子

線程池中常用參數(shù)釋義:

  • corePoolSize 線程池的基本大?。ê诵木€程池大?。?/li>
  • maximumPool 線程池最大大小
  • keepAliveTime 線程池中超過corePoolSize數(shù)目的空閑線程的最大存活時間
  • unit keepAliveTime參數(shù)的時間單位
  • workQueue 任務阻塞隊列
  • threadFactory 新建線程的工廠
  • handler 當提交任務數(shù)超過maximumPoolSizeworkQueue之和時,任務會交給RejectedExecutionHandler來處理

線程池調度原理:

線程池調度原理.jpg

飽和策略:

  1. AboutPolicy 直接拋出RejectedExecutionExeception異常,阻止系統(tǒng)的正常運行
  2. CallerRunsPolicy 將任務回退到調用者
  3. DisOldestPolicy 丟掉等待最久的任務
  4. DisCardPolicy 直接丟棄任務

注: 以上四種拒絕策略均實現(xiàn)的RejectedExencutionHandler

注:iOS開發(fā)中不會直接接觸到線程池,這是因為GCD已經包含了線程池的管理,我們常用的就是GCD(NSOperation底層也是GCD),所以只需要通過GCD獲取線程來執(zhí)行任務即可。

繼續(xù)探索多線程更底層的知識可以嘗試搜索Java Posix

2.6 iOS的幾種多線程技術方案

iOS多線程API.jpg
  1. pthread:即POSIX Thread,是線程的POSIX標準,是一套通用的多線程API,可以在Unix/Linux/Windows等平臺跨平臺使用。iOS中基本不使用。
  2. NSThread:蘋果封裝的面向對象的線程類,可以直接操作線程,比起GCD,NSThread效率更高,由程序員自行創(chuàng)建,當線程中的任務執(zhí)行完畢后,線程會自動退出,程序員也可手動管理線程的生命周期。使用頻率較低。
  3. GCD:全稱Grand Central Dispatch,由C語言實現(xiàn),是蘋果為多核的并行運算提出的解決方案,GCD會自動利用更多的CPU內核,自動管理線程的聲明周期,程序員只需要告訴GCD需要執(zhí)行的任務,無需編寫任何管理線程的代碼。GCD也是iOS使用頻率最高的多線程技術。
  4. NSOperation:基于GCD封裝的面向對象的多線程類,相較于GCD提供了很多方便的API,使用頻率較高。

3. 線程間的通訊

3.1 幾種線程間的通訊方式

通過上面的介紹我們對線程有了基本的了解,那么線程之間是如何通訊的呢?其實我們都知道的就是在子線程獲取數(shù)據,然后切換到主線程進行刷新UI,其實這只是表象,并不是真正的線程通訊方式,如果在面試中這樣回答基本就是回家等消息了。那么線程之間倒地是如何通訊的呢?在蘋果官方文檔中給我們列出了線程間通訊的幾種方式:

線程間通訊的幾種方式.jpg

線程之間有許多通信方式,每種方式都有其優(yōu)缺點。配置線程本地存儲列出了您可以在OS x中使用的最常見的通信機制(除了消息隊列和Cocoa分布式對象,這些技術在iOS中也可用)。這個表中的技術是按照復雜度遞增的順序列出的。其中后兩種只能在OS X中使用。

  • Direct messaging:這個就是我們常用的selector系列,例如-performSelector:
  • Global variables, shared memory, and objects: 直接通過全局變量、共享內存等方式,但這種方式會造成資源搶奪,涉及到線程安全問題。
  • Conditions:條件鎖,一種特殊的鎖,我們可以使用它來控制線程何時執(zhí)行特定部分的代碼。
  • Run loop sources: 自定義Runloop來設置用于在線程上接收應用程序特定消息的循環(huán)源,因為它們是事件驅動的,所以運行循環(huán)源會在無事可做時將線程自動休眠,從而提高線程的效率。
  • Ports and sockets: 通過端口和套接字來實現(xiàn)線程間通訊。

4. iOS中使用線程間通訊示例(使用NSPort)

此處的例子是通過一個PortPerson對象跟ViewController進行發(fā)消息的實例。

代碼中首先將VCself.myPort添加到主線程的Runloop中,然后起新線程讓PersonVC發(fā)送消息,VC在收到消息后也像Person發(fā)送一條消息,在此過程中并沒有切換線程的代碼,通過打印線程可以看到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. 創(chuàng)建自己port
        self.myPort = [NSMachPort port];
        //5. 設置port的代理回調對象
        self.myPort.delegate = self;
        //6. 完成向主線程port發(fā)送消息
        [self sendPortMessage];
    }
    
}

- (void)sendPortMessage {
 
    NSData *data1 = [@"這是一條port信息" dataUsingEncoding:NSUTF8StringEncoding];
//    NSData *data2 = [@"data2" dataUsingEncoding:NSUTF8StringEncoding];

    NSMutableArray *array  =[[NSMutableArray alloc]initWithArray:@[data1,self.myPort]];
    // 發(fā)送消息到VC的主線程
    // 第一個參數(shù):發(fā)送時間。
    // msgid 消息標識。
    // components,發(fā)送消息附帶參數(shù)。
    // reserved:為頭部預留的字節(jié)數(shù)
    [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];
    // 創(chuàng)建主線程的port,子線程通過此端口發(fā)送消息給主線程
    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(@"傳過來的數(shù)據有誤");
        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

注意?。。?/strong>

  1. NSPort對象必須添加到要接收消息的線程的Runloop中。
  2. 接收消息的對象實現(xiàn)NSPortDelegate協(xié)議的-handlePortMessage:方法來獲取消息內容。!
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 多線程掌握:GCD + NSOperation + SDWebImage + 單例模式 基本概念 進程:在系統(tǒng)中正...
    Arthur澪閱讀 171評論 0 0
  • 什么是進程? 進程是指系統(tǒng)中正在運行的一個程序,每個進程間是獨立的,每個進程均運行在其專用且受保護的內存空間內. ...
    星辰流轉輪回閱讀 239評論 0 0
  • 系列文章: 多線程 多線程 pthread、NSThread 多線程 GCD 多線程 NSOperation 多線...
    林安530閱讀 419評論 0 0
  • 學習線程之前我們先溫習下 進程: 線程: 多線程的原理:同一時間,cpu只能處理一條線程,只有一條線程在工作,多線...
    Jey閱讀 208評論 0 0
  • 多線程,一直是解決內存暴增方法的重點,圖片、視頻、大量數(shù)據的的下載現(xiàn)在總結一下 基本概念 進程:應用程序的執(zhí)行實例...
    艷曉閱讀 308評論 0 0

友情鏈接更多精彩內容