iOS-多線程(一)-原理

多線程(一)-原理
多線程(二)-GCD基礎
多線程(三)-GCD函數
多線程(四)-GCD定時器安全

基本概念

線程

線程是進程的基本執行單元,一個進程的全部任務都在線程中執行 進程要想執行任務,必須得有線程,進程至少要有一條線程。 程序啓動會默認開啓一條線程,這條線程被稱爲主線程或UI線程。服務器

進程

進程是指在系統中正在運行的一個應用程序。每一個進程之間是獨立的,每一個進程均運行在其專用的且受保護的內存。多線程

線程與進程的關係

  • 地址空間: 同一進程的線程共享本進程的地址空間,而進程之間則是獨立的地址空間。
  • 資源擁有: 同一進程內的線程共享本進程的資源如內存、I/Ocpu等,可是進程之間的資源是獨立的。
  • 一個進程崩潰後,在保護模式下不會對其餘進程產生影響,可是一個線程崩潰整個進程 都死掉。因此多進程要比多線程健壯。
  • 進程切換時,消耗的資源大,效率高。因此涉及到頻繁的切換時,使用線程要好於進程。一樣若是要求同時進行而且又要共享某些變量的併發操做,只能用線程不能用進程。
  • 執行過程: 每一個獨立的進程有一個程序運行的入口、順序執行序列和程序入口。可是線 程不能獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。
  • 線程是處理器調度的基本單位,可是進程不是。

例如,有一個進程是打掃教室,其中掃地、擦桌子、擦玻璃、擦黑板就是不一樣的線程。併發

多線程原理

CPU在單位時間片裏在各個線程之間切換。函數

線程的生命週期包含5個階段,包括:新建、準備、運行、阻塞、銷燬。oop

  • 新建:建立線程
  • 準備:調用的線程的start()方法後,此時線程處於等待CPU分配資源階段,誰先搶的CPU資源,誰開始執行。
  • 運行:當線程被調度並得到CPU資源時,便進入運行狀態,run方法定義了線程的操做和功能。
  • 阻塞:在運行的時候,可能由於某些緣由致使運行狀態的線程被阻塞,好比sleep()wait()以後線程就處於了阻塞狀態,這個時候須要其餘機制將處於阻塞狀態的線程喚醒,好比調用notify或者notifyAll()方法。喚醒的線程不會馬上執行run方法,它們要再次等待CPU分配資源進入運行狀態。 銷燬:若是線程正常執行完畢後或線程被提早強制性的終止或出現異常致使結束,那麼線程就要被銷燬,釋放資源。

整個流程以下所示:post

多線程處理是經過線程池來進行的,處理過程當中將任務添加到隊列,而後在建立線程後自動啓動這些任務。線程池中的線程都是後臺線程。每一個線程都有默認的堆棧大小,以默認的優先級運行,並處在多線程單元中。線程池的性能

飽和策略分爲四種,均實現的RejectedExecutionHandler接口:atom

  • AbortPolicy 直接拋出RejectedExecutionExeception異常來阻止系統正常運行
  • CallerRunsPolicy 將任務回退到調用者
  • DisOldestPolicy 丟掉等待最久的任務
  • DisCardPolicy 直接丟棄任務

多線程的優缺點

優勢:spa

  • 能適當提升程序的執行效率
  • 能適當提升資源的利用率(CPU,內存)
  • 線程上的任務執行完成後,線程會自動銷燬

缺點:

  • 開啓線程須要佔用必定的內存空間(默認狀況下,每個線程都佔512KB)
  • 若是開啓大量的線程,會佔用大量的內存空間,下降程序的性能
  • 線程越多,CPU在調用線程上的開銷就越大
  • 程序設計更加複雜,好比線程間的通訊、多線程的數據共享

iOS多線程技術

iOS多線程有不少技術,下面作了一下對比:

對比得知,咱們通常主要使用的仍是GCDNSOperation

線程和runloop的關係

  • runloop與線程是一一對應的,一個runloop對應一個核心的線程。爲何說是核心的,是由於runloop是能夠嵌套的,可是核心的只能有一個,他們的關係保存在一個全局的字典裏。
  • runloop是來管理線程的,當線程的runloop被開啓後,線程會在執行完任務後進入休眠狀態,有了任務就會被喚醒去執行任務。
  • runloop在第一次獲取時被建立,在線程結束時被銷燬。
  • 對於主線程來講,runloop在程序一啓動就默認建立好了。
  • 對於子線程來講,runloop是懶加載的,只有當咱們使用的時候纔會建立,因此在子線程用定時器要注意,確保子線程的runloop被建立,否則定時器不會回調。

線程間的通信

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

  1. performSelector:onThread。好比說有AB兩個線程,咱們能夠在A線程裏使用該方法,調用B的方法,而後再在B線程裏使用該方法調用A線程的方法,這樣就實現了線程之間的通信。
- (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. NSPortNSPort使用的方式爲接收線程中註冊NSPort,在另外的線程中使用此port發送消息,則被註冊線程會收到相應消息,而後最終在主線程裏調用某個回調函數。

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

NSMachPort的方式以下:

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

//1. 建立主線程的port
// 子線程經過此端口發送消息給主線程
self.aPort = [NSMachPort port];
//2. 設置port的代理回調對象
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類中建立一個響應線程的方法
@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 {
        // 設置子線程名字
        [[NSThread currentThread] setName:@"TPersonThread"];
        // 開啓runloop
        [[NSRunLoop currentRunLoop] run];
        // 建立本身port
        self.bPort = [NSMachPort port];
        // 完成向主線程port發送消息
        
        NSData *data = [@"portPass" dataUsingEncoding:NSUTF8StringEncoding];

        NSMutableArray *array  =[[NSMutableArray alloc]initWithArray:@[data,self.bPort]];
        // 發送消息到A線程
        // 第一個參數:發送時間。
        // msgid 消息標識。
        // components,發送消息附帶參數。
        // reserved:爲頭部預留的字節數
        [port sendBeforeDate:[NSDate date]
                       msgid:10086
                  components:array
                        from:self.bPort
                     reserved:0];
    }
}
複製代碼
  • 2.3 在A線程實現的代理方法,獲取回調的信息
#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(@"傳過來的數據有誤");
        return;
    }
}
複製代碼

Tips

atomicnonatomic

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

通常iOS開發中,關於線程相關須要注意的是:

  • 儘可能全部屬性都聲明爲nonatomic
  • 儘可能避免多線程搶奪同一塊資源
  • 儘可能將加鎖、資源搶奪的業務邏輯交給服務器端處理,減少移動客戶端的壓力
相關文章
相關標籤/搜索