iOS多執行緒那點事兒

weixin_34087301發表於2016-10-15

前言


最近一直在做專案,也是經常有用到一些執行緒的知識,抽出一些時間來對執行緒做一下彙總,由淺入深,也很方便新手入門。

概述


多執行緒開發在iOS中有著舉足輕重的位置,學習好多執行緒是每一個iOS Developer必須要掌握的技能。

概念


程式

  • 程式代表當前執行的一個程式

  • 是系統分配資源的基本單位

  • 每個程式之間是獨立的,每個程式均執行在其專用且受保護的記憶體空間內

  • 比如同時開啟QQ、Xcode,系統就會分別啟動2個程式

2595997-91d28dd8860997cb.png
  • 程式可以理解為一個工廠
  • 通過“活動監視器”可以檢視Mac系統中所開啟的程式

執行緒

  • 執行緒是程式的基本執行單元,一個程式(程式)的所有任務都線上程中執行

  • 一個程式含有一個執行緒或多個執行緒

  • 應用程式開啟後會預設開闢一個執行緒叫做主執行緒或者UI執行緒

  • 比如使用酷狗播放音樂、使用迅雷下載電影,都需要線上程中執行

2595997-8cb50d843973167f.png
  • 執行緒可以理解為工廠裡的工人

序列

  • 多個任務按順序執行

  • 類似於一個視窗辦公排隊

  • 也就是說,在同一時間內,1個執行緒只能執行1個任務

  • 比如在1個執行緒中下載3個檔案(分別是檔案A、檔案B、檔案C)就要依次執行

2595997-a08d2f0e08ad28dd.png

並行

  • 多個任務同一時間一起執行

  • 類似於多個視窗辦公

  • 比如同時開啟3條執行緒分別下載3個檔案(分別是檔案A、檔案B、檔案C),同時執行

2595997-fac3dec9cf4023a1.png

併發

  • 很多人容易認為併發和並行是一個意思,但實際上他們有本質的區別

  • 併發看起來像多個任務同一時間一起執行

  • 但實際上是CPU快速的輪轉切換造成的假象

多執行緒

  • 本質

    • 在一個程式中開啟多個執行緒併發執行
  • 原理

    • 同一時間,CPU只能處理1條執行緒,只有1條執行緒在工作(執行)

    • 多執行緒併發(同時)執行,其實是CPU快速地在多條執行緒之間排程(切換)

    • 如果CPU排程執行緒的時間足夠快,就造成了多執行緒併發執行的假象

  • 優點

    • 能適當提高程式的執行效率

    • 能適當提高資源利用率(CPU、記憶體利用率)

  • 缺點

    • 執行緒需要耗費系統資源

    • 主執行緒需要消耗棧空間的1MB資源

    • 其他執行緒每個消耗512KB資源

    • 程式設計更加複雜:比如執行緒之間的通訊、多執行緒的資料共享

不推薦過多使用

主執行緒

  • 概念
    • 一個iOS程式執行後,預設會開啟1條執行緒,稱為“主執行緒”或“UI執行緒”
  • 作用
    • 顯示\重新整理UI介面

    • 處理UI事件(比如點選事件、滾動事件、拖拽事件等)

  • 注意
    • 別將比較耗時的操作放到主執行緒中

    • 耗時操作會卡住主執行緒,嚴重影響UI的流暢度,給使用者一種“卡”的壞體驗

耗時操作執行

  • 如果放在主執行緒
2595997-ee36add8e92d7285.png
  • 因為在主執行緒中的任務是按照順序依次執行的

  • 如果把耗時操作放在主執行緒裡,會等待它執行完後才能執行其他操作

  • 如果在等待執行完畢的時間裡點選了其他控制元件就會給使用者一種卡住的感覺,嚴重影響使用者體驗


  • 如果放在子執行緒
2595997-1a034c9b92eafdfb.png
  • 在使用者點選按鈕的時候就會做出反應

  • 兩個執行緒同時執行,互不影響

多執行緒的實現


方案

2595997-170842a60366dce6.png

PThread

  • 簡單瞭解即可
- (IBAction)buttonClick:(id)sender {
    pthread_t thread;
    pthread_create(&thread, NULL, run, NULL);

    pthread_t thread2;
    pthread_create(&thread2, NULL, run, NULL);
}

void * run(void *param)
{
    for (NSInteger i = 0; i<50000; i++) {
        NSLog(@"------buttonClick---%zd--%@", i, [NSThread currentThread]);
    }
    return NULL;
}

NSThread

  • 基本建立方法

  • 一個NSThread物件就代表一條執行緒

  • 建立、啟動執行緒

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

// 需要手動開啟執行緒
[thread start];
  • 主執行緒相關用法
// 獲得主執行緒
+ (NSThread *)mainThread;

// 是否為主執行緒
- (BOOL)isMainThread;

// 是否為主執行緒
+ (BOOL)isMainThread;
  • 獲得當前執行緒
NSThread *current = [NSThread currentThread];
  • 執行緒的名字
- (void)setName:(NSString *)name;
- (NSString *)name;
  • 其他建立方法

    • 建立執行緒後自動啟動執行緒

[NSThread detachNewThreadSelector:@selector(threadAction)toTarget:self withObject:nil]


- 隱式建立並啟動執行緒

[self performSelectorInBackground:@selector(threadAction) withObject:nil];


- 上述2種建立執行緒方式的優缺點

- 優點:簡單快捷

- 缺點:無法對執行緒進行更詳細的設定

- 執行緒睡眠

[NSThread sleepForTimeInterval:2]; // 讓執行緒睡眠2秒(阻塞2秒)

[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];

- 退出當前執行緒

[NSThread exit];

- 設定執行緒優先順序 (預設0.5)

thread.threadPriority = 1.0f;


####GCD

- 這裡寫了關於GCD的詳細介紹,包括GCD死鎖等問題。

    - http://www.jianshu.com/p/f13c4b336d34

#### NSOperation

- NSOperation 是蘋果公司對 GCD 的封裝,完全物件導向,所以使用起來更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分別對應 GCD 的 任務 和 佇列 。操作步驟也很好理解。

- NSOperation也有兩個概念,佇列和任務。
- 主佇列
    - [NSOperationQueue mainQueue]
    - 凡是新增到主佇列中的任務(NSOperation),都會放到主執行緒中執行
- 非主佇列(其他佇列)
    - [[NSOperationQueue alloc] init]
    - 同時包含了:序列、併發功能
    - 新增到這種佇列中的任務(NSOperation),就會自動放到子執行緒中執行


- 系統為我們提供了NSOperation的子類我們可以直接使用
- 當某個任務經常使用,我們可以自定義NSOperation,在這個NSOperation中的main方法中寫任務。
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationAction) object:nil];

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"haha ----- %@", [NSThread currentThread]);
}];

// 佇列
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];

// 任務1完事以後才執行任務2
[operation2 addDependency:operation1];

// 設定最大併發數()
operationQueue.maxConcurrentOperationCount = 4;

[operationQueue addOperationWithBlock:^{
    NSLog(@"hello ------ %@", [NSThread currentThread]);
}];

[operationQueue addOperation:operation1];
[operationQueue addOperation:operation2];

#### NSOperation 對比 GCD

- GCD效率更高,使用起來也很方便

- NSOperation物件導向,可讀性更高,架構更清晰,對於複雜多執行緒場景,如併發中存在序列,和設定最大併發數,擁有現在的API,使用起來特別簡單

# 執行緒的狀態
-----

![](http://upload-images.jianshu.io/upload_images/2595997-c99d3dd0bb01ffe4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

#### 控制執行緒的狀態
------

- 啟動執行緒

// 進入就緒狀態->執行狀態。 當執行緒執行完畢自動進入死亡狀態。

  • (void)start;

- 阻塞(暫停)執行緒

// 進入阻塞狀態

  • (void)sleepUntilData:(NSDate *)data;
  • (void)sleepForTimeInterval:(NSTimeInterval)ti;

- 強制停止狀態

// 進入死亡狀態

  • (void)exit;

- 注意
  - 一旦執行緒停止(死亡)了,就不能再次開啟任務

#### 多執行緒的安全隱患問題
-------
- 1塊資源可能會被多個執行緒共享,也就是多個執行緒可能會訪問同一塊資源

- 比如多個執行緒訪問同一個物件、同一個變數、同一個檔案

- 當多個執行緒訪問同一塊資源時,很容易引發資料錯亂和資料安全問題

- 比如下面這個例子

![](http://upload-images.jianshu.io/upload_images/2595997-ad279c08b04af078.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- 執行緒A從記憶體中拿出一個Integer型別的值,為17;進行加1操作後變為18,然後返回給記憶體

- 執行緒B同時從記憶體中拿出一個Integer型別的值,為17;進行加1操作後變為18,然後返回給記憶體

- 出現的問題就是分別在兩個執行緒中做了加1操作,然而最後的結果只顯示了一次加1的結果,出現了資料錯亂的問題,正確結果應該是變為20

-------

- 解決方案,使用互斥鎖

![](http://upload-images.jianshu.io/upload_images/2595997-7a4df7f138bec881.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- 執行緒A進入記憶體讀取值得時候先加一把鎖,讓外界無法拿到17進行修改,等執行緒A對17做完加1操作後返回給記憶體後,再解鎖.

- 此時如果執行緒B來記憶體中想要修改17的時候,發現上了鎖,只能等待執行緒A做完操作後才能修改值,而A操作完後此時的值已經變成了18,B從記憶體中要修改的話,直接從記憶體中拿到的就是18,開始修改,然後加鎖不讓其他執行緒進來。改完過後,在解鎖。方便下一個執行緒進來修改。

#### 互斥鎖
------

- 使用前提
  - 多條執行緒搶奪同一塊資源
- 相關專業術語
  - 執行緒同步
- 互斥鎖使用格式

@synchronized(鎖物件)
{
//需要鎖定的程式碼
}


- 注意:
  - 鎖定1份程式碼只用一把鎖,用多把鎖是無效的

  - 為了保證唯一性,鎖物件一般填self
- 互斥鎖的優缺點
  - 優點:
    - 能有效防止因多執行緒搶奪資源造成的資料安全問題
  - 缺點
    - 需要消耗大量的CPU資源


####執行緒間通訊
-----
- 概念
  - 在1個程式中,執行緒往往不是孤立存在的,多個執行緒之間需要經常進行通訊
- 表現
  - 1個執行緒傳遞資料給另1個執行緒

  - 在1個執行緒中執行完特定任務後,轉到另1個執行緒繼續執行任務
- 執行緒間通訊常用方法

  • (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
  • (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wait;

- 例子

    ![](http://upload-images.jianshu.io/upload_images/2595997-b62b48d63d07cccf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    - 在子執行緒中做耗時的操作,比如下載圖片

    - 在子執行緒中操作完後要回到主執行緒做UI的重新整理操作(顯示圖片)

- 另外一種執行緒通訊方法(利用NSPort)
  - 如果子執行緒想要傳資料給主執行緒,主執行緒就要返回一個Port物件讓子執行緒去擁有,子執行緒通過Port物件對主執行緒進行操作。

![](http://upload-images.jianshu.io/upload_images/2595997-2e137ee0e5e71421.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
   
# 心靈雞湯
------
禪師問:你覺得是一錠金子好,還是一堆爛泥好呢?壯士答:當然是金子啊!禪師笑曰:假如你是一顆種子呢?壯士答:別他媽搞笑了,這錠金子我要定了,快給我鬆手,鬆手啊魂淡!!

難受的時候摸摸自己的胸,告訴自己是個漢子,要堅強~

![](http://upload-images.jianshu.io/upload_images/2595997-51f8a282f95b7f8c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

相關文章