iOS-多線程(一)原理

基本概念

線程

線程是進(jìn)程的基本執(zhí)行單元,一個進(jìn)程的所有任務(wù)都在線程中執(zhí)行 進(jìn)程要想執(zhí)行任務(wù),必須得有線程,進(jìn)程至少要有一條線程。
程序啟動會默認(rèn)開啟一條線程,這條線程被稱為主線程或UI線程。

進(jìn)程

進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個應(yīng)用程序。每個進(jìn)程之間是獨(dú)立的,每個進(jìn)程均運(yùn)行在其專用的且受保護(hù)的內(nèi)存。

線程與進(jìn)程的關(guān)系

  • 地址空間: 同一進(jìn)程的線程共享本進(jìn)程的地址空間,而進(jìn)程之間則是獨(dú)立的地址空間。
  • 資源擁有: 同一進(jìn)程內(nèi)的線程共享本進(jìn)程的資源如內(nèi)存、I/O、cpu等,但是進(jìn)程之間的資源是獨(dú)立的。
  • 一個進(jìn)程崩潰后,在保護(hù)模式下不會對其他進(jìn)程產(chǎn)生影響,但是一個線程崩潰整個進(jìn)程 都死掉。所以多進(jìn)程要比多線程健壯。
  • 進(jìn)程切換時,消耗的資源大,效率高。所以涉及到頻繁的切換時,使用線程要好于進(jìn)程。同樣如果要求同時進(jìn)行并且又要共享某些變量的并發(fā)操作,只能用線程不能用進(jìn)程。
  • 執(zhí)行過程: 每個獨(dú)立的進(jìn)程有一個程序運(yùn)行的入口、順序執(zhí)行序列和程序入口。但是線 程不能獨(dú)立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個線程執(zhí)行控制。
  • 線程是處理器調(diào)度的基本單位,但是進(jìn)程不是。

例如,有一個進(jìn)程是打掃教室,其中掃地、擦桌子、擦玻璃、擦黑板就是不同的線程。

多線程原理

CPU在單位時間片里在各個線程之間切換。

線程的生命周期包含5個階段,包括:新建、準(zhǔn)備、運(yùn)行、阻塞、銷毀。

  • 新建:創(chuàng)建線程
  • 準(zhǔn)備:調(diào)用的線程的start()方法后,此時線程處于等待CPU分配資源階段,誰先搶的CPU資源,誰開始執(zhí)行。
  • 運(yùn)行:當(dāng)線程被調(diào)度并獲得CPU資源時,便進(jìn)入運(yùn)行狀態(tài),run方法定義了線程的操作和功能。
  • 阻塞:在運(yùn)行的時候,可能因?yàn)槟承┰驅(qū)е逻\(yùn)行狀態(tài)的線程被阻塞,比如sleep()、wait()之后線程就處于了阻塞狀態(tài),這個時候需要其他機(jī)制將處于阻塞狀態(tài)的線程喚醒,比如調(diào)用notify或者notifyAll()方法。喚醒的線程不會立刻執(zhí)行run方法,它們要再次等待CPU分配資源進(jìn)入運(yùn)行狀態(tài)。
    銷毀:如果線程正常執(zhí)行完畢后或線程被提前強(qiáng)制性的終止或出現(xiàn)異常導(dǎo)致結(jié)束,那么線程就要被銷毀,釋放資源。

整個流程如下所示:

image

多線程處理是通過線程池來進(jìn)行的,處理過程中將任務(wù)添加到隊(duì)列,然后在創(chuàng)建線程后自動啟動這些任務(wù)。線程池中的線程都是后臺線程。每個線程都有默認(rèn)的堆棧大小,以默認(rèn)的優(yōu)先級運(yùn)行,并處在多線程單元中。線程池的

image

飽和策略分為四種,均實(shí)現(xiàn)的RejectedExecutionHandler接口:

  • AbortPolicy 直接拋出RejectedExecutionExeception異常來阻止系統(tǒng)正常運(yùn)行
  • CallerRunsPolicy 將任務(wù)回退到調(diào)用者
  • DisOldestPolicy 丟掉等待最久的任務(wù)
  • DisCardPolicy 直接丟棄任務(wù)

多線程的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

  • 能適當(dāng)提高程序的執(zhí)行效率
  • 能適當(dāng)提高資源的利用率(CPU,內(nèi)存)
  • 線程上的任務(wù)執(zhí)行完成后,線程會自動銷毀

缺點(diǎn):

  • 開啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,每一個線程都占512KB)
  • 如果開啟大量的線程,會占用大量的內(nèi)存空間,降低程序的性能
  • 線程越多,CPU在調(diào)用線程上的開銷就越大
  • 程序設(shè)計(jì)更加復(fù)雜,比如線程間的通信、多線程的數(shù)據(jù)共享

iOS多線程技術(shù)

iOS多線程有很多技術(shù),下面做了一下對比:

image

對比得知,我們一般主要使用的還是GCDNSOperation。

線程和runloop的關(guān)系

  • runloop與線程是一一對應(yīng)的,一個runloop對應(yīng)一個核心的線程。為什么說是核心的,是因?yàn)?code>runloop是可以嵌套的,但是核心的只能有一個,他們的關(guān)系保存在一個全局的字典里。
  • runloop是來管理線程的,當(dāng)線程的runloop被開啟后,線程會在執(zhí)行完任務(wù)后進(jìn)入休眠狀態(tài),有了任務(wù)就會被喚醒去執(zhí)行任務(wù)。
  • runloop在第一次獲取時被創(chuàng)建,在線程結(jié)束時被銷毀。
  • 對于主線程來說,runloop在程序一啟動就默認(rèn)創(chuàng)建好了。
  • 對于子線程來說,runloop是懶加載的,只有當(dāng)我們使用的時候才會創(chuàng)建,所以在子線程用定時器要注意,確保子線程的runloop被創(chuàng)建,不然定時器不會回調(diào)。

線程間的通訊

多線程之間的通訊,除過簡單的獲取主線程,還有2種方式。

  1. performSelector:onThread。比如說有A、B兩個線程,我們可以在A線程里使用該方法,調(diào)用B的方法,然后再在B線程里使用該方法調(diào)用A線程的方法,這樣就實(shí)現(xiàn)了線程之間的通訊。
- (void)performSelectorOnMainThread:(SEL)aSelector      
                         withObject:(nullable id)arg
                         waitUntilDone:(BOOL)wait

- (void)performSelector:(SEL)aSelector 
               onThread:(NSThread *)thr 
               withObject:(nullable id)arg waitUntilDone:(BOOL)wait
  1. NSPort。NSPort使用的方式為接收線程中注冊NSPort,在另外的線程中使用此port發(fā)送消息,則被注冊線程會收到相應(yīng)消息,然后最終在主線程里調(diào)用某個回調(diào)函數(shù)。

NSPort有3個子類,NSSocketPort、NSMessagePortNSMachPort,但在iOS下只有NSMachPort可用。

NSMachPort的方式如下:

  • 2.1 創(chuàng)建A線程的port,將port加入runloop
@property (nonatomic, strong) NSPort *aPort;
@property (nonatomic, strong) TPerson *person;

//1. 創(chuàng)建主線程的port
// 子線程通過此端口發(fā)送消息給主線程
self.aPort = [NSMachPort port];
//2. 設(shè)置port的代理回調(diào)對象
self.aPort.delegate = self;
//3. 把port加入runloop,接收port消息
[[NSRunLoop currentRunLoop] addPort:self.aPort forMode:NSDefaultRunLoopMode];
    
self.person = [[TPerson alloc] init];
[NSThread detachNewThreadSelector:@selector(personLaunchThreadWithPort:) toTarget:self.person withObject:self.aPort];
  • 2.2 在person類中創(chuàng)建一個響應(yīng)線程的方法
@interface TPerson : NSObject
- (void)personLaunchThreadWithPort:(NSPort *)port;
@end


#import "TPerson.h"

@interface TPerson()
@property (nonatomic, strong) NSPort *bPort;
@end

@implementation TPerson
- (void)personLaunchThreadWithPort:(NSPort *)port{
    @autoreleasepool {
        // 設(shè)置子線程名字
        [[NSThread currentThread] setName:@"TPersonThread"];
        // 開啟runloop
        [[NSRunLoop currentRunLoop] run];
        // 創(chuàng)建自己port
        self.bPort = [NSMachPort port];
        // 完成向主線程port發(fā)送消息
        
        NSData *data = [@"portPass" dataUsingEncoding:NSUTF8StringEncoding];

        NSMutableArray *array  =[[NSMutableArray alloc]initWithArray:@[data,self.bPort]];
        // 發(fā)送消息到A線程
        // 第一個參數(shù):發(fā)送時間。
        // msgid 消息標(biāo)識。
        // components,發(fā)送消息附帶參數(shù)。
        // reserved:為頭部預(yù)留的字節(jié)數(shù)
        [port sendBeforeDate:[NSDate date]
                       msgid:10086
                  components:array
                        from:self.bPort
                     reserved:0];
    }
}
  • 2.3 在A線程實(shí)現(xiàn)的代理方法,獲取回調(diào)的信息
#pragma mark - NSMachPortDelegate

- (void)handlePortMessage:(NSPortMessage *)message{
    NSLog(@"VC == %@",[NSThread currentThread]);
    NSLog(@"從person 傳過來一些信息:");
//    NSLog(@"localPort == %@",[message valueForKey:@"localPort"]);
//    NSLog(@"remotePort == %@",[message valueForKey:@"remotePort"]);
//    NSLog(@"receivePort == %@",[message valueForKey:@"receivePort"]);
//    NSLog(@"sendPort == %@",[message valueForKey:@"sendPort"]);
//    NSLog(@"msgid == %@",[message valueForKey:@"msgid"]);
//    NSLog(@"components == %@",[message valueForKey:@"components"]);
    
    NSArray *messageArr = [message valueForKey:@"components"];
    NSString *dataStr   = [[NSString alloc] initWithData:messageArr.firstObject  encoding:NSUTF8StringEncoding];
    NSLog(@"傳過來一些信息 :%@",dataStr);
    NSPort  *destinPort = [message valueForKey:@"remotePort"];
    
    if(!destinPort || ![destinPort isKindOfClass:[NSPort class]]){
        NSLog(@"傳過來的數(shù)據(jù)有誤");
        return;
    }
}

Tips

atomicnonatomic

  • nonatomic非原子性,非線程安全,但是性能高一些,適合內(nèi)存小的移動設(shè)備
  • atomic原子屬性(線程安全),針對多線程設(shè)計(jì)的,是默認(rèn)的。保證同一時間只有一個線程能夠?qū)懭?但是同一個時間多個線程都可以取值),在屬性的 setter 方法中添加了一把鎖(自旋鎖)。屬于單寫多讀:單個線程寫入,多個線程可以讀取。但是需要消耗大量的資源。

一般iOS開發(fā)中,關(guān)于線程相關(guān)需要注意的是:

  • 盡量所有屬性都聲明為nonatomic
  • 盡量避免多線程搶奪同一塊資源
  • 盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動客戶端的壓力
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 又來到了一個老生常談的問題,應(yīng)用層軟件開發(fā)的程序員要不要了解和深入學(xué)習(xí)操作系統(tǒng)呢? 今天就這個問題開始,來談?wù)劜?..
    tangsl閱讀 4,332評論 0 23
  • 本文將從以下幾個部分來介紹多線程。 第一部分介紹多線程的基本原理。 第二部分介紹Run loop。 第三部分介紹多...
    曲年閱讀 1,347評論 2 14
  • 翻譯來源: RunLoops Run Loops RunLoops是與線程緊密相關(guān)的基礎(chǔ)架構(gòu)的一部分,簡稱運(yùn)行循環(huán)...
    AlexCorleone閱讀 694評論 0 1
  • 1. 簡介 用戶打開瀏覽器,其實(shí)就是打開了瀏覽器應(yīng)用程序。那么什么是程序呢?我們常說瀏覽器是多線程的,JS 是單線...
    love丁酥酥閱讀 3,669評論 0 6
  • 1.1 多線程介紹 學(xué)習(xí)多線程之前,我們先要了解幾個關(guān)于多線程有關(guān)的概念。 進(jìn)程:進(jìn)程指正在運(yùn)行的程序。確切的來說...
    Pecksniff1994閱讀 1,649評論 0 2

友情鏈接更多精彩內(nèi)容