多線程與網絡A

多線程基礎

進程

  • 什麼是進程
    • 進程是指在系統中正在運行的一個應用程序
    • 每個進程之間是獨立的,每個進程均運行在其專用且受保護的內存空間內
  • 通過“活動監視器”可以查看Mac系統中所開啓的進程

線程

  • 什麼是線程
    • 1個進程要想執行任務,必須得有線程(每1個進程至少要有1條線程)
    • 一個進程(程序)的所有任務都在線程中執行

線程的串行

  • 1個線程中任務的執行是串行的
    • 如果要在1個線程中執行多個任務,那麼只能一個一個地按順序執行這些任務
    • 也就是說,在同一時間內,1個線程只能執行1個任務

線程是進程中的1條執行路徑

多線程

  • 什麼是多線程
    • 1個進程中可以開啓多條線程,多條線程可以並行(同時)執行不同的任務
    • 進程 → 車間,線程 → 車間工人
    • 多線程技術可以提高程序的執行效率
  • 多線程的原理
    • 同一時間,CPU只能處理1條線程,只有1條線程在工作(執行)
    • 多線程併發(同時)執行,其實是CPU快速地在多條線程之間調度(切換)
    • 如果CPU調度線程的時間足夠快,就造成了多線程併發執行的假象
    • 思考:如果線程非常非常多,會發生什麼情況?
      • CPU會在N多線程之間調度,CPU會累死,消耗大量的CPU資源
      • 每條線程被調度執行的頻次會降低(線程的執行效率降低)

多線程的優缺點

  • 多線程的優點
    • 能適當提高程序的執行效率
    • 能適當提高資源利用率(CPU、內存利用率)
  • 多線程的缺點
    • 創建線程是有開銷的,iOS下主要成本包括:內核數據結構(大約1KB)、棧空間(子線程512KB、主線程1MB,也可以使用-setStackSize:設置,但必須是4K的倍數,而且最小是16K),創建線程大約需要90毫秒的創建時間
    • 如果開啓大量的線程,會降低程序的性能
    • 線程越多,CPU在調度線程上的開銷就越大
    • 程序設計更加複雜:比如線程之間的通信、多線程的數據共享

多線程在iOS開發中的應用

  • 什麼是主線程
    • 一個iOS程序運行後,默認會開啓1條線程,稱爲“主線程”或“UI線程”
  • 主線程的主要作用
    • 顯示\刷新UI界面
    • 處理UI事件(比如點擊事件、滾動事件、拖拽事件等)
  • 主線程的使用注意
    • 別將比較耗時的操作放到主線程中
    • 耗時操作會卡住主線程,嚴重影響UI的流暢度,給用戶一種“卡”的壞體驗

iOS中多線程的實現方案

這裏寫圖片描述

多線程NSThread

創建和啓動線程

  • 一個NSThread對象就代表一條線程
  • 創建、啓動線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
// 線程一啓動,就會在線程thread中執行self的run方法
  • 主線程相關用法
+ (NSThread *)mainThread; // 獲得主線程
- (BOOL)isMainThread; // 是否爲主線程
+ (BOOL)isMainThread; // 是否爲主線程
  • 獲得當前線程
NSThread *current = [NSThread currentThread];
  • 線程的名字
- (void)setName:(NSString *)n;
- (NSString *)name;

其他創建線程方式

  • 創建線程後自動啓動線程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
  • 隱式創建並啓動線程
[self performSelectorInBackground:@selector(run) withObject:nil];
  • 上述2種創建線程方式的優缺點
    • 優點:簡單快捷
    • 缺點:無法對線程進行更詳細的設置

線程的狀態

 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
 [thread start];

這裏寫圖片描述

控制線程狀態

  • 啓動線程
- (void)start; 
// 進入就緒狀態 -> 運行狀態。當線程任務執行完畢,自動進入死亡狀態
  • 阻塞(暫停)線程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 進入阻塞狀態
  • 強制停止線程
+ (void)exit;
// 進入死亡狀態

注意:一旦線程停止(死亡)了,就不能再次開啓任務

多線程的安全隱患

  • 資源共享
    • 1塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源
    • 比如多個線程訪問同一個對象、同一個變量、同一個文件
  • 當多個線程訪問同一塊資源時,很容易引發數據錯亂和數據安全問題

安全隱患分析

這裏寫圖片描述

安全隱患解決 – 互斥鎖

這裏寫圖片描述

  • 互斥鎖使用格式
    • @synchronized(鎖對象) { // 需要鎖定的代碼 }
    • 注意:鎖定1份代碼只用1把鎖,用多把鎖是無效的
  • 互斥鎖的優缺點
    • 優點:能有效防止因多線程搶奪資源造成的數據安全問題
    • 缺點:需要消耗大量的CPU資源
  • 互斥鎖的使用前提:多條線程搶奪同一塊資源
  • 相關專業術語:線程同步
    • 線程同步的意思是:多條線程在同一條線上執行(按順序地執行任務)
    • 互斥鎖,就是使用了線程同步技術

原子和非原子屬性

  • OC在定義屬性時有nonatomic和atomic兩種選擇

    • atomic:原子屬性,爲setter方法加鎖(默認就是atomic)
    • nonatomic:非原子屬性,不會爲setter方法加鎖
  • nonatomic和atomic對比

    • atomic:線程安全,需要消耗大量的資源
    • nonatomic:非線程安全,適合內存小的移動設備
  • iOS開發的建議
    • 所有屬性都聲明爲nonatomic
    • 儘量避免多線程搶奪同一塊資源
    • 儘量將加鎖、資源搶奪的業務邏輯交給服務器端處理,減小移動客戶端的壓力

線程間通信

  • 什麼叫做線程間通信
    • 在1個進程中,線程往往不是孤立存在的,多個線程之間需要經常進行通信
  • 線程間通信的體現
    • 1個線程傳遞數據給另1個線程
    • 在1個線程中執行完特定任務後,轉到另1個線程繼續執行任務
  • 線程間通信常用方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

多線程GCD

  • 什麼是GCD
    • 全稱是Grand Central Dispatch,可譯爲“牛逼的中樞調度器”
    • 純C語言,提供了非常多強大的函數
  • GCD的優勢
    • GCD是蘋果公司爲多核的並行運算提出的解決方案
    • GCD會自動利用更多的CPU內核(比如雙核、四核)
    • GCD會自動管理線程的生命週期(創建線程、調度任務、銷燬線程)
    • 程序員只需要告訴GCD想要執行什麼任務,不需要編寫任何線程管理代碼

任務和隊列

  • GCD中有2個核心概念
    • 任務:執行什麼操作
    • 隊列:用來存放任務
  • GCD的使用就2個步驟
    • 定製任務
    • 將任務添加到隊列中
      • GCD會自動將隊列中的任務取出,放到對應的線程中執行
      • 任務的取出遵循隊列的FIFO原則:先進先出,後進後出

執行任務

  • GCD中有2個用來執行任務的常用函數

    • 用同步的方式執行任務

      dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
      //  queue:隊列
      //  block:任務
      
    • 用異步的方式執行任務

      dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
      
  • 同步和異步的區別
    • 同步:只能在當前線程中執行任務,不具備開啓新線程的能力
    • 異步:可以在新的線程中執行任務,具備開啓新線程的能力
  • GCD中還有個用來執行任務的函數:
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
// 在前面的任務執行結束後它才執行,而且它後面的任務等它執行完成之後纔會執行
這個queue不能是全局的併發隊列

隊列的類型

  • GCD的隊列可以分爲2大類型

    • 併發隊列(Concurrent Dispatch Queue)

      • 可以讓多個任務併發(同時)執行(自動開啓多個線程同時執行任務)
      • 併發功能只有在異步(dispatch_async)函數下才有效
    • 串行隊列(Serial Dispatch Queue)

      • 讓任務一個接着一個地執行(一個任務執行完畢後,再執行下一個任務)

併發隊列

  • 使用dispatch_queue_create函數創建隊列
dispatch_queue_t
dispatch_queue_create(const char *label, // 隊列名稱 
dispatch_queue_attr_t attr); // 隊列的類型
  • 創建併發隊列
dispatch_queue_t queue = dispatch_queue_create("com.baidu.queue", DISPATCH_QUEUE_CONCURRENT);
  • GCD默認已經提供了全局的併發隊列,供整個應用使用,可以無需手動創建

    • 使用dispatch_get_global_queue函數獲得全局的併發隊列

      dispatch_queue_t dispatch_get_global_queue(
      dispatch_queue_priority_t priority, // 隊列的優先級
      unsigned long flags); // 此參數暫時無用,用0即可
      
      //  獲得全局併發隊列
      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
      
    • 全局併發隊列的優先級

    
    #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
    
    
    #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(中)
    
    
    #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
    
    
    #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 後臺
    
    

串行隊列

  • GCD中獲得串行有2種途徑

    • 使用dispatch_queue_create函數創建串行隊列

      // 創建串行隊列(隊列類型傳遞NULL或者DISPATCH_QUEUE_SERIAL)
      dispatch_queue_t queue = dispatch_queue_create("com.baidu.queue", NULL); 
      
    • 使用主隊列(跟主線程相關聯的隊列)

      • 主隊列是GCD自帶的一種特殊的串行隊列
      • 放在主隊列中的任務,都會放到主線程中執行
      • 使用dispatch_get_main_queue()獲得主隊列
        dispatch_queue_t queue = dispatch_get_main_queue();
        

各種隊列的執行效果

這裏寫圖片描述

線程間通信示例

  • 從子線程回到主線程
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 執行耗時的異步操作...
      dispatch_async(dispatch_get_main_queue(), ^{
        // 回到主線程,執行UI刷新操作
        });
});

延時執行

  • 調用NSObject的方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒後再調用self的run方法
  • 使用GCD函數
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒後執行這裏的代碼...
});
  • 使用NSTimer
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:NO];

一次性代碼

  • 使用dispatch_once函數能保證某段代碼在程序運行過程中只被執行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只執行1次的代碼(這裏面默認是線程安全的)
});

快速迭代

  • 使用dispatch_apply函數能進行快速迭代遍歷
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){
    // 執行10次代碼,index順序不確定
});

隊列組

  • 有這麼1種需求
    • 首先:分別異步執行2個耗時的操作
    • 其次:等2個異步操作都執行完畢後,再回到主線程執行操作
  • 如果想要快速高效地實現上述需求,可以考慮用隊列組
dispatch_group_t group =  dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 執行1個耗時的異步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 執行1個耗時的異步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的異步操作都執行完畢後,回到主線程...
});

單例模式

  • 單例模式的作用

    • 可以保證在程序運行過程,一個類只有一個實例,而且該實例易於供外界訪問
    • 從而方便地控制了實例個數,並節約系統資源
  • 單例模式的使用場合

    • 在整個應用程序中,共享一份資源(這份資源只需要創建初始化1次)
  • ARC中,單例模式的實現

    • 在.m中保留一個全局的static的實例static id _instance;
    • 重寫allocWithZone:方法,在這裏創建唯一的實例(注意線程安全)

      + (instancetype)allocWithZone:(struct _NSZone *)zone
      {
      static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
          _instance = [super allocWithZone:zone];
      });
      return _instance;
      }
      
    • 提供1個類方法讓外界訪問唯一的實例

      + (instancetype)sharedInstance
      {
      static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
          _instance = [[self alloc] init];
      });
      return _instance;
      }
      
    • 實現copyWithZone:方法

      - (id)copyWithZone:(struct _NSZone *)zone
      {
      return _instance;
      }
      

多線程NSOperation

  • NSOperation的作用
    • 配合使用NSOperation和NSOperationQueue也能實現多線程編程
  • NSOperation和NSOperationQueue實現多線程的具體步驟
    • 先將需要執行的操作封裝到一個NSOperation對象中
    • 然後將NSOperation對象添加到NSOperationQueue中
    • 系統會自動將NSOperationQueue中的NSOperation取出來
    • 將取出的NSOperation封裝的操作放到一條新線程中執行

NSOperation的子類

  • NSOperation是個抽象類,並不具備封裝操作的能力,必須使用它的子類
  • 使用NSOperation子類的方式有3種
    • NSInvocationOperation
    • NSBlockOperation
    • 自定義子類繼承NSOperation,實現內部相應的方法

NSInvocationOperation

  • 創建NSInvocationOperation對象
    - (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;

  • 調用start方法開始執行操作
    - (void)start;

    • 一旦執行操作,就會調用target的sel方法
  • 注意
    • 默認情況下,調用了start方法後並不會開一條新線程去執行操作,而是在當前線程同步執行操作
    • 只有將NSOperation放到一個NSOperationQueue中,纔會異步執行操作

NSBlockOperation

  • 創建NSBlockOperation對象

    + (id)blockOperationWithBlock:(void (^)(void))block;
    
  • 通過addExecutionBlock:方法添加更多的操作

    - (void)addExecutionBlock:(void (^)(void))block;
    

    注意:只要NSBlockOperation封裝的操作數 > 1,就會異步執行操作

NSOperationQueue

  • NSOperationQueue的作用

    • NSOperation可以調用start方法來執行任務,但默認是同步執行的
    • 如果將NSOperation添加到NSOperationQueue(操作隊列)中,系統會自動異步執行NSOperation中的操作
  • 添加操作到NSOperationQueue中

    - (void)addOperation:(NSOperation *)op;
    - (void)addOperationWithBlock:(void (^)(void))block;
    

最大併發數

  • 什麼是併發數
    • 同時執行的任務數
    • 比如,同時開3個線程執行3個任務,併發數就是3
  • 最大併發數的相關方法

    - (NSInteger)maxConcurrentOperationCount;
    - (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
    

隊列的取消、暫停、恢復

  • 取消隊列的所有操作

    - (void)cancelAllOperations;
    //  提示:也可以調用NSOperation的- (void)cancel方法取消單個操作
    
  • 暫停和恢復隊列

    - (void)setSuspended:(BOOL)b; // YES代表暫停隊列,NO代表恢復隊列
    - (BOOL)isSuspended;
    

操作依賴

  • NSOperation之間可以設置依賴來保證執行順序

    • 比如一定要讓操作A執行完後,才能執行操作B,可以這麼寫[operationB addDependency:operationA]; // 操作B依賴於操作A
  • 可以在不同queue的NSOperation之間創建依賴關係
    這裏寫圖片描述

操作的監聽

  • 可以監聽一個操作的執行完畢

    - (void (^)(void))completionBlock;
    - (void)setCompletionBlock:(void (^)(void))block;

自定義NSOperation

  • 自定義NSOperation的步驟很簡單
    • 重寫- (void)main方法,在裏面實現想執行的任務
  • 重寫- (void)main方法的注意點
    • 自己創建自動釋放池(因爲如果是異步操作,無法訪問主線程的自動釋放池)
    • 經常通過- (BOOL)isCancelled方法檢測操作是否被取消,對取消做出響應

自定義NSOperation下載圖片思路 – 無沙盒緩存

這裏寫圖片描述

自定義NSOperation下載圖片思路 – 有沙盒緩存

這裏寫圖片描述

多線程RunLoop

待更新,請關注博文“ios開發進階之多線程03 RunLoop”

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章