iOS多線程技術方案

iOS多線程技術方案

目錄

1、多線程簡介

一、多線程的由來
二、耗時操做的模擬試驗
三、進程和線程
四、多線程的概念及原理
五、多線程的優缺點和一個Tip
六、主線程
七、技術方案
2、Pthread
---
一、函數
二、參數和返回值
三、使用
3、NSThread
---
一、建立一個新的線程
二、線程的狀態
三、線程的屬性
4、互斥鎖
---
一、訪問共享資源引入問題!
二、互斥鎖介紹
三、互斥鎖原理
四、互斥鎖和自旋鎖
5、GCD
---
一、GCD介紹
二、GCD的兩個核心
三、函數
四、串行隊列和併發隊列
五、主隊列
六、全局隊列
七、GCD總結
6、NSOperation
---
一、NSOperation簡介
二、核心概念
三、操做步驟
四、NSInvocationOperation
五、NSBlockOperation
7、案例
---
***ios

1、多線程簡介

一、多線程的由來

一個進程(進程)在執行一個線程(線程中有不少函數或方法(後面簡稱Function))的時候,其中有一個Function執行的時候須要消耗一些時間,可是這個線程又必須同時執行這個Function以後的Function,問題來了,一個線程中的任何一個Function都必須等待其執行完成後才能執行後面的Function,若是要同時執行兩個或者多個Function,那麼,就必須多開一個或者多個線程,這就是多線程的產生。我想多線程最開始的誕生就是由這而來吧!程序員

二、耗時操做的模擬試驗

2.1 循環測試

代碼算法

int main(int argc, const char * argv[]) {
@autoreleasepool {
    NSLog(@"bengin");
    for (int i = 0; i < 10000000; i++) {
    }
    NSLog(@"end");
}
return 0;
}

控制檯編程

2016-02-16 13:51:54.140 Test[1670:603696] bengin
2016-02-16 13:51:54.160 Test[1670:603696] end
Program ended with exit code: 0

結論一:循環一億次耗時0.02秒,計算機的運行速度是很是快的安全

2.2 操做棧區

代碼多線程

int main(int argc, const char * argv[]) {
@autoreleasepool {
    NSLog(@"bengin");
    for (int i = 0; i < 10000000; i++) {
        int n = 1;
    }
    NSLog(@"end");
}
return 0;
}

控制檯併發

2016-02-16 13:57:37.589 Test[1734:631377] bengin
2016-02-16 13:57:37.612 Test[1734:631377] end
Program ended with exit code: 0

結論二:對棧區操做一億次,耗時0.023秒框架

2.3 操做常量區

代碼:異步

int main(int argc, const char * argv[]) {
@autoreleasepool {
    NSLog(@"bengin");
    for (int i = 0; i < 10000000; i++) {
        NSString *str = @"hellow";
    }
    NSLog(@"end");
}
return 0;
}

控制檯async

2016-02-16 14:03:59.003 Test[1763:659287] bengin
2016-02-16 14:03:59.113 Test[1763:659287] end
Program ended with exit code: 0

結論三:對常量區操做一億次,耗時0.11秒

2.4 操做堆區

代碼

int main(int argc, const char * argv[]) {
@autoreleasepool {
    NSLog(@"bengin");
    for (int i = 0; i < 10000000; i++) {
        NSString *str = [NSString stringWithFormat:@"%d",i];
    }
    NSLog(@"end");
}
return 0;
}

控制檯

2016-02-16 14:09:03.673 Test[1786:673719] bengin
2016-02-16 14:09:10.705 Test[1786:673719] end
Program ended with exit code: 0

結論四:對堆區操做一億次耗時7秒多一些,較慢!

2.5 I/O操做

代碼

int main(int argc, const char * argv[]) {
@autoreleasepool {
    NSLog(@"bengin");
    for (int i = 0; i < 10000000; i++) {
        NSLog(@"%d",i);
    }
    NSLog(@"end");
}
return 0;
}

控制檯輸出!正在跑中,一億次!!!先看截圖
CPU
1
再看內存

1
好吧,還在跑,如今已經達到10分鐘了,怕心疼本本炸掉!stop。。。
結論五:I/O操做很是慢,一億次10分鐘也沒能跑完!

最終結論:經過以上結論1、2、3、4、五得出一個結論,各個區的執行效率:棧區>常量區>堆區>I/O操做。同時也說明了一個問題,執行不一樣的方法會產什麼耗時操做。這是,爲了解決耗時操做問題,多線程閃亮誕生!

三、進程和線程

先說說進程和線程吧!

3.1 進程

3.1.1 進程的概念:系統中正在運行的應用程序。

3.1.2 進程的特色:每一個進程都運行在其專用且受保護的內存空間,不一樣的進程之間相互獨立,互不干擾。

3.2 線程

3.2.1 線程的概念:線程是進程的執行任務的基本單元,一個進程的全部任務都是在線程中執行的。(每個進程至少要有一條線程)。

3.2.2 線程的特色:線程在執行任務的時候是按順序執行的。若是要讓一條線程執行多個任務,那麼只能一個一個地而且按順序執行這些任務。也就是說,在同一時間,一條線程只能執行一個任務。

咱們能夠經過Mac中的活動監視器查看進程和線程,下圖!
1

四、多線程的概念及原理

4.1 多線程概念:1個進程能夠開啓多條線程,多條線程能夠同時執行不一樣的任務。

4.2 多線程原理:

1

前提是在單核CPU的狀況下,同一時間,CPU只能處理一條線程,也就是說只有一條線程在執行任務。多線程同時執行,那是不可能的!可是是CPU快速地在多條線程之間進行調度和切換執行任務。若是CPU調度線程的速度足夠快,就會形成多條線程同時執行任務的」假象」,這種假象,就被美譽爲:多線程!

五、多線程的優缺點和一個Tip

5.1 多線程的優勢

  • 能夠適當提升程序的執行效率
  • 也能夠適當提升資源的利用率(CPU、內存利用率)

    5.2 多線程的缺點

  • 開啓一條線程須要佔用必定的內存空間(默認狀況下,每一條線程都佔用512KB),若是開啓大量的線程,會佔用大量的內存空間,從而下降程序的性能。
  • 線程越多,CPU在調度和切換線程上的開銷就會越大。
  • 線程數越多,程序的設計會越複雜。

    5.3 Tip

  • 開啓新的線程就會消耗資源,可是卻能夠提升用戶體驗。在保證良好的用戶體驗的前提下,能夠適當地開線程,通常開3-6條。

1

  • 開啓一條新的線程,默認狀況下,一條線程都是佔用512KB,可是官方的文檔裏面給出的說明卻不是,爲了得出真相,下面作個小小的測試!
    代碼

    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    /** 操做主線程 /
    NSLog(@"主線程默認 %tu", [NSThread currentThread].stackSize / 1024);
    // 設置主線程的stackSize
    [NSThread currentThread].stackSize = 1024
    1024;
    NSLog(@"主線程修改 %tu", [NSThread currentThread].stackSize / 1024);

    /** 操做子線程 */
      NSThread *thread = [[NSThread alloc] init];
      NSLog(@"thread默認 %tu", thread.stackSize / 1024);
      // 設置子線程的stackSize
      thread.stackSize = 8 * 1024;
      NSLog(@"thread修改 %tu", thread.stackSize / 1024);
      [thread start];

    }
    return 0;
    }
    控制檯

    2016-02-17 08:36:02.652 Test[609:110129] 主線程默認 512
    2016-02-17 08:36:02.654 Test[609:110129] 主線程修改 1024
    2016-02-17 08:36:02.654 Test[609:110129] thread默認 512
    2016-02-17 08:36:02.654 Test[609:110129] thread修改 8

結論七:證實了,無論什麼線程,默認都是512,最小爲8.多是官方文檔沒有及時更新吧!

六、主線程

6.1 主線程的概念:

一個應用程序在啓動運行後,系統會自動開啓1條線程,這條稱爲」主線程」。

6.2 主線程的做用:主線程的做用主要用於處理UI界面刷新和UI時間!

6.3 結論:主線程上不能執行耗時操做,這樣會形成界面卡頓,給用戶一種很差的體驗。

七、技術方案

1
***

2、Pthread

一、函數

pthread_create(pthread_t *restrict, const pthread_attr_t *restrict, void *(*)(void *), void *restrict)

二、參數和返回值

  • pthread_t *restrict 線程編號的地址
  • const pthread_attr_t *restrict 線程的屬性
  • void *(*)(void *) 線程要執行的函數void * (*) (void *)
    • int * 指向int類型的指針 void * 指向任何類型的指針 有點相似OC中的id
  • void *restrict 要執行的函數的參數

  • 返回值 int類型 0是成功 非0 是失敗

三、使用

代碼

#import <Foundation/Foundation.h>
#import <pthread/pthread.h>

void *demo(void *param) {
NSString *name = (__bridge NSString *)(param);

NSLog(@"hello %@ %@",name,[NSThread currentThread]);
return NULL;
}

int main(int argc, const char * argv[]) {
@autoreleasepool {
    //建立子線程
    pthread_t pthread; //線程編號
    
    NSString *test = @"test";
    int result =  pthread_create(&pthread, NULL, demo, (__bridge void *)(test));
    NSLog(@"Began  %@",[NSThread currentThread]);
    
    if (result == 0) {
        NSLog(@"成功");
    }else {
        NSLog(@"失敗");
    }
}
return 0;
}

控制檯

2016-02-16 22:00:57.401 Test[888:42585] Began  <NSThread: 0x100502d70>{number = 1, name = main}
2016-02-16 22:00:57.403 Test[888:42615] hello test <NSThread: 0x100102a30>{number = 2, name = (null)}
2016-02-16 22:00:57.403 Test[888:42585] 成功
  • __bridge 橋接,把OC中的對象,傳遞給c語言的函數,使用__bridge
    ***

    3、NSThread

    一、建立一個新的線程

  • 方式一

    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
  • 方式二

    [NSThread detachNewThreadSelector:@selector(demo) toTarget:self withObject:nil];
  • 方式三

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

二、線程的狀態

線程狀態分爲五種

  • 建立 New
  • 就緒 Runnable
  • 運行 Running

    - (void)start;
  • 阻塞(暫停) Blocked

    + (void)sleepUntilDate:(NSDate *)date;
      + (void)sleepForTimeInterval:(NSTimeInterval)ti;
  • 死亡 Dead

    + (void)exit;

三、線程的屬性

線程有兩個重要的屬性:名稱和優先級

3.1 名稱 name

設置線程名用於記錄線程,在出現異常時能夠DeBug

3.2 優先級,也叫作「服務質量」。threadPriority,取值0到1.

優先級或者服務質量高的,能夠優先調用,只是說會優先調用,可是不是百分之百的優先調用,這裏存在一個機率問題,內核裏的算法調度線程的時候,只是把優先級做爲一個考慮因素,還有不少個因數會決定哪一個線程優先調用。這點得注意注意!!!

下面是測試代碼

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//新建狀態
NSThread *test1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
test1.name = @"test1";
//線程的優先級
test1.threadPriority = 1.0;
//就緒狀態
[test1 start];


//新建狀態
NSThread *test2= [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
test2.name = @"test2";
test2.threadPriority = 0;
//就緒狀態
[test2 start];
}

//線程執行完成以後會自動銷燬
- (void)demo {
for (int i = 0; i < 20; i++) {
    NSLog(@"%d--%@",i,[NSThread currentThread]);
}
}

控制檯

2016-02-16 22:43:28.182 05-線程狀態[1241:78688] 0--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.182 05-線程狀態[1241:78689] 0--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.182 05-線程狀態[1241:78688] 1--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.182 05-線程狀態[1241:78688] 2--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.182 05-線程狀態[1241:78689] 1--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.183 05-線程狀態[1241:78688] 3--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.183 05-線程狀態[1241:78689] 2--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.183 05-線程狀態[1241:78688] 4--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.183 05-線程狀態[1241:78688] 5--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.183 05-線程狀態[1241:78689] 3--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.183 05-線程狀態[1241:78688] 6--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.183 05-線程狀態[1241:78688] 7--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.183 05-線程狀態[1241:78689] 4--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.183 05-線程狀態[1241:78688] 8--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78688] 9--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78688] 10--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78689] 5--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.184 05-線程狀態[1241:78688] 11--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78689] 6--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.184 05-線程狀態[1241:78688] 12--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78688] 13--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78689] 7--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.184 05-線程狀態[1241:78688] 14--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78688] 15--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.185 05-線程狀態[1241:78688] 16--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78689] 8--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.185 05-線程狀態[1241:78688] 17--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.185 05-線程狀態[1241:78688] 18--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.185 05-線程狀態[1241:78689] 9--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.185 05-線程狀態[1241:78688] 19--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.185 05-線程狀態[1241:78689] 10--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.186 05-線程狀態[1241:78689] 11--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.186 05-線程狀態[1241:78689] 12--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.186 05-線程狀態[1241:78689] 13--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.186 05-線程狀態[1241:78689] 14--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.187 05-線程狀態[1241:78689] 15--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.187 05-線程狀態[1241:78689] 16--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.187 05-線程狀態[1241:78689] 17--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.187 05-線程狀態[1241:78689] 18--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.187 05-線程狀態[1241:78689] 19--<NSThread: 0x7fead050a250>{number = 3, name = test2}

結論六:優先級高,不必定先執行,只能說明先執行的機率要大一些!!!


4、互斥鎖

一、訪問共享資源引入問題!

1.1 問題?

不一樣的線程要訪問共享的資源,並且對共享的資源作操做,因爲上面結論六得出服務質量和優先級不能決定線程執行的前後順序,那麼問題來了,一個線程對共享資源作了修改,而另一個線程拿到的是未被修改以前資源,這是這個線程也對該資源作了修改,如今請問,兩個線程都對該資源作了不一樣的修改,那麼這個修改應該算誰的?!?!這就是問題所在!!!

1.2 問題分析

1

1.3 問題解決

1
這個文檔裏盜的圖!

解決方案很簡單,就是用一把鎖鎖住共享資源,等待線程1對其操做完畢後再打開,讓線程2來執行,這就是傳說中的互斥鎖!!!

二、互斥鎖介紹

2.1 互斥鎖代碼

@synchronized(鎖對象) { 須要鎖定的代碼  }

2.2 互斥鎖的做用

能夠防止因多線程執行順序不定致使的搶奪資源形成的數據安全的問題

2.3 真相:互斥鎖其實就是同步的意思,也就是按順序執行!

三、互斥鎖原理

每一個NSObject對象內部都有一把鎖,當線程要進入synchronized到對象的時候就要判斷,鎖是否被打開,若是打開,進入執行,若是鎖住,繼續等待,這就是互斥鎖的原理!

四、互斥鎖和自旋鎖

自旋鎖就是atomic!

4.1 原子屬性和非原子屬性(nonatomic 和 atomic)

  • nonatomic:非原子屬性,不會爲 setter 方法加鎖。
  • atomic: 原子屬性,爲 setter 方法加鎖(默認就是atomic)。
    • 經過給 setter 加鎖,能夠保證同一時間只有一個線程可以執行寫入操做(setter),可是同一時間容許多個線程執行讀取操做(getter)。atomic自己就有一把自旋鎖。
      這個特色叫作」單寫多讀」: 單個線程寫入,多個線程讀取。
    • atomic 只能保證寫入數據的時候是安全的,但不能保證同時讀寫的時候是安全的。因此,不常使用!

4.2 nonatomic 和 atomic 的對比

atomic:線程安全(執行setter方法的時候),須要消耗大量的資源。

nonatomic:非線程安全,適合內存小的移動設備。

4.3 互斥鎖和自旋鎖的區別

互斥鎖

若是發現其它線程正在執行鎖定代碼,線程會進入休眠(阻塞狀態),等其它線程時間片到了打開鎖後,線程就會被喚醒(執行)。

自旋鎖

若是發現有其它線程正在執行鎖定代碼,線程會以死循環的方式,一直等待鎖定的代碼執行完成。
***

5、GCD

一、GCD介紹

全稱Grand Central Dispatch,可翻譯爲」牛逼的中樞調度器」

純C語言開發,是蘋果公司爲多核的並行運算提出的解決方案,會自動利用更多的CPU內核(好比雙核、四核),能夠自動管理線程的生命週期(建立線程、調度任務、銷燬線程)。

二、GCD的兩個核心

2.1 任務

  • 執行的操做,在GCD中,任務是經過 block來封裝的。而且任務的block沒有參數也沒有返回值。

2.2 隊列

  • 存聽任務

包括

  • 串行隊列
  • 併發隊列
  • 主隊列
  • 全局隊列

隊列的類型
1

三、函數

3.1 GCD函數

3.1.1 同步 dispatch_sync

同步:任務會在當前線程執行,由於同步函數不具有開新線程的能力。
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

3.1.2 異步 dispatch_async

異步:任務會在子線程執行,由於異步函數具有開新線程的能力。
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

3.2 GCD使用步驟:

  • 建立隊列,或則獲取隊列
  • 建立任務
  • 將任務添加到隊列中
    • GCD會自動將隊列中的任務取出,放到對應的線程中執行
    • 任務取出遵循隊列的FIFO原則:先進先出,後進後出

示例代碼

// 1. 獲取全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 2. 建立任務
dispatch_block_t task = ^ {
    NSLog(@"hello %@", [NSThread currentThread]);
};
// 3. 將任務添加到隊列,而且指定執行任務的函數
dispatch_async(queue, task);
一般寫成一句代碼
dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"hello %@", [NSThread currentThread]);
    });

四、串行隊列和併發隊列

4.1 串行隊列 (Serial Dispatch Queue)

4.1.1 特色

  • 先進先出,按照順序執行,而且一次只能調用一個任務
  • 不管隊列中所指定的執行任務的函數是同步仍是異步,都必須等待前一個任務執行完畢才能夠調用後面的人

4.1.2 建立一個串行隊列

方法一
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
方法二
dispatch_queue_t queue = dispatch_queue_create("test", NULL);

4.1.3 串行隊列,同步執行

代碼:

// 一、建立串行隊列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
// 二、將任務添加到隊列,而且指定同步執行
for (int i = 0; i < 10; i++) {
    dispatch_sync(queue, ^{
        NSLog(@"%@--%d",[NSThread currentThread],i);
    });
}

打印結果:

2016-02-25 16:31:07.849 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--0
2016-02-25 16:31:07.849 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--1
2016-02-25 16:31:07.849 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--2
2016-02-25 16:31:07.849 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--3
2016-02-25 16:31:07.849 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--4
2016-02-25 16:31:07.849 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--5
2016-02-25 16:31:07.850 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--6
2016-02-25 16:31:07.850 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--7
2016-02-25 16:31:07.850 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--8
2016-02-25 16:31:07.850 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--9

結論:串行隊列,同步執行,不開新線程,按順序執行

4.1.4 串行隊列,異步執行

代碼:

// 一、建立串行隊列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
// 二、將任務添加到隊列,而且指定同步執行
for (int i = 0; i < 10; i++) {
    dispatch_async(queue, ^{
        NSLog(@"%@--%d",[NSThread currentThread],i);
    });
}

打印結果:

2016-02-25 17:08:32.167 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--0
2016-02-25 17:08:32.168 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--1
2016-02-25 17:08:32.168 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--2
2016-02-25 17:08:32.168 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--3
2016-02-25 17:08:32.168 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--4
2016-02-25 17:08:32.168 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--5
2016-02-25 17:08:32.169 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--6
2016-02-25 17:08:32.169 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--7
2016-02-25 17:08:32.169 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--8
2016-02-25 17:08:32.169 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--9

結論:串行隊列,異步執行,開啓一條新的線程,按順序執行

4.2 併發隊列 (Concurrent Dispatch Queue)

4.2.1 特色

  • 併發同時調度隊列中的任務去執行
  • 若是當前調度的任務是同步執行的,會等待當前任務執行完畢後,再調度後續的任務
  • 若是當前調度的任務是異步執行的,同時底層線程池有可用的線程資源,就不會等待當前任務,直接調度任務到新線程去執行。

4.2.2 建立併發隊列

dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

4.2.3 併發隊列,同步執行

代碼:

// 1. 建立併發隊列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
// 2. 將任務添加到隊列, 而且指定同步執行
for (int i = 0; i < 10; i++) {
    dispatch_sync(queue, ^{
        NSLog(@"%@ %d", [NSThread currentThread], i);
    });
}

輸出:

2016-02-25 17:18:38.039 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 0
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 1
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 2
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 3
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 4
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 5
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 6
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 7
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 8
2016-02-25 17:18:38.041 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 9

結論:併發隊列,同步執行,不開線程,順序執行

4.2.4 併發隊列,異步執行

代碼:

// 1. 建立併發隊列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
// 2. 將任務添加到隊列, 而且指定同步執行
for (int i = 0; i < 10; i++) {
    dispatch_async(queue, ^{
        NSLog(@"%@ %d", [NSThread currentThread], i);
    });
}

輸出:

2016-02-25 17:22:59.357 test[1992:403694] <NSThread: 0x7fe531c1a9b0>{number = 7, name = (null)} 6
2016-02-25 17:22:59.356 test[1992:403684] <NSThread: 0x7fe531d18fa0>{number = 3, name = (null)} 1
2016-02-25 17:22:59.356 test[1992:403689] <NSThread: 0x7fe534300610>{number = 5, name = (null)} 3
2016-02-25 17:22:59.356 test[1992:403683] <NSThread: 0x7fe531e94d80>{number = 2, name = (null)} 0
2016-02-25 17:22:59.356 test[1992:403692] <NSThread: 0x7fe531e9df80>{number = 6, name = (null)} 4
2016-02-25 17:22:59.356 test[1992:403693] <NSThread: 0x7fe531d18f40>{number = 8, name = (null)} 5
2016-02-25 17:22:59.356 test[1992:403695] <NSThread: 0x7fe5343015e0>{number = 9, name = (null)} 7
2016-02-25 17:22:59.357 test[1992:403688] <NSThread: 0x7fe531c16e30>{number = 4, name = (null)} 2
2016-02-25 17:22:59.357 test[1992:403694] <NSThread: 0x7fe531c1a9b0>{number = 7, name = (null)} 9
2016-02-25 17:22:59.357 test[1992:403696] <NSThread: 0x7fe531c237a0>{number = 10, name = (null)} 8

結論:開啓足夠多的線程,不按照順序執行
CPU在調度的時候以最高效的方式調度和執行任務,因此會開啓多條線程,由於併發,執行順序不必定

五、主隊列

5.1 主隊列

主隊列是系統提供的,無需本身建立,能夠經過dispatch_get_main_queue()函數來獲取。

5.2 特色

  • 添加到主隊列的任務只能由主線程來執行。
  • 先進先出的,只有當主線程的代碼執行完畢後,主隊列纔會調度任務到主線程執行

    5.3 主隊列 異步執行

    代碼

    // 1. 獲取主隊列
      dispatch_queue_t q = dispatch_get_main_queue();
      // 2. 將任務添加到主隊列, 而且指定異步執行
      for (int i = 0; i < 10; i++) {
      dispatch_async(q, ^{
          NSLog(@"%@ %d", [NSThread currentThread], i);
      });
      }
      // 先執行完這句代碼, 纔會執行主隊列中的任務
      NSLog(@"hello %@", [NSThread currentThread]);

打印

2016-02-25 21:10:43.293 test[773:786816] hello <NSThread: 0x7ff158c05940>{number = 1, name = main}
2016-02-25 21:10:43.295 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 0
2016-02-25 21:10:43.295 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 1
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 2
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 3
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 4
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 5
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 6
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 7
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 8
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 9

打印結果得出的一些結論

  • 在主線程順序執行,不開啓新的線程
  • 主隊列的特色:只有當主線程空閒時,主隊列纔會調度任務到主線程執行
  • 主隊列就算是異步執行也不會開啓新的線程
  • 主隊列至關於一個全局的串行隊列
  • 主隊列和串行隊列的區別
    • 串行隊列:必須等待一個任務執行完畢,纔會調度下一個任務。
    • 主隊列:若是主線程上有代碼執行,主隊列就不調度任務。

5.4 主隊列 同步執行(死鎖)

代碼

NSLog(@"begin");
// 1. 獲取主隊列
dispatch_queue_t q = dispatch_get_main_queue();
// 2. 將任務添加到主隊列, 而且指定同步執行
// 死鎖
for (int i = 0; i < 10; i++) {
    dispatch_sync(q, ^{
        NSLog(@"%@ %d", [NSThread currentThread], i);
    });
}
NSLog(@"end");

打印

2016-02-25 21:19:25.986 test[791:790967] begin

打印結果能夠看出,不是想要的結果,這時候發生了死鎖
在主線程執行,主隊列同步執行任務,會發生死鎖,主線程和主隊列同步任務相互等待,形成死鎖

解決辦法

代碼

NSLog(@"begin");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"--- %@", [NSThread currentThread]);
    // 1. 獲取主隊列
    dispatch_queue_t q = dispatch_get_main_queue();
    // 2. 將任務添加到主隊列, 而且指定同步執行
    // 死鎖
    for (int i = 0; i < 10; i++) {
        dispatch_sync(q, ^{
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }
});
NSLog(@"end");

打印

2016-02-25 21:23:23.205 test[803:794826] begin
2016-02-25 21:23:23.206 test[803:794826] end
2016-02-25 21:23:23.206 test[803:794866] --- <NSThread: 0x7f8830514cb0>{number = 2, name = (null)}
2016-02-25 21:23:23.209 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 0
2016-02-25 21:23:23.209 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 1
2016-02-25 21:23:23.209 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 2
2016-02-25 21:23:23.209 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 3
2016-02-25 21:23:23.209 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 4
2016-02-25 21:23:23.210 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 5
2016-02-25 21:23:23.210 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 6
2016-02-25 21:23:23.210 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 7
2016-02-25 21:23:23.210 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 8
2016-02-25 21:23:23.210 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 9

打印結果能夠看出,當咱們將主隊列同步執行任務放到子線程去執行,就不會出現死鎖。因爲將主隊列同步放到了子線程中執行,主隊列同步任務沒法阻塞主線程執行代碼,所以主線程能夠將主線程上的代碼執行完畢。當主線程執行完畢以後,就會執行主隊列裏面的任務。

六、全局隊列

全局隊列是系統提供的,無需本身建立,能夠直接經過dispatch_get_global_queue(long identifier, unsigned long flags);函數來獲取。

6.1 全局隊列 異步執行

代碼

// 1. 獲取全局隊列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// 2. 將任務添加到全局隊列, 異步執行
for (int i = 0; i < 10; i++) {
    dispatch_async(q, ^{
        NSLog(@"%d %@", i, [NSThread currentThread]);
    });
}

打印輸出

2016-02-25 21:29:06.978 test[816:799523] 1 <NSThread: 0x7fd428e15760>{number = 3, name = (null)}
2016-02-25 21:29:06.978 test[816:799530] 4 <NSThread: 0x7fd428d2fbb0>{number = 6, name = (null)}
2016-02-25 21:29:06.978 test[816:799522] 0 <NSThread: 0x7fd428f094e0>{number = 2, name = (null)}
2016-02-25 21:29:06.978 test[816:799529] 3 <NSThread: 0x7fd428c0e1b0>{number = 5, name = (null)}
2016-02-25 21:29:06.978 test[816:799532] 6 <NSThread: 0x7fd428f06740>{number = 7, name = (null)}
2016-02-25 21:29:06.978 test[816:799533] 7 <NSThread: 0x7fd428d37be0>{number = 8, name = (null)}
2016-02-25 21:29:06.978 test[816:799531] 5 <NSThread: 0x7fd428e0c490>{number = 9, name = (null)}
2016-02-25 21:29:06.978 test[816:799526] 2 <NSThread: 0x7fd428d3e4b0>{number = 4, name = (null)}
2016-02-25 21:29:06.979 test[816:799534] 8 <NSThread: 0x7fd428d36ab0>{number = 10, name = (null)}
2016-02-25 21:29:06.979 test[816:799523] 9 <NSThread: 0x7fd428e15760>{number = 3, name = (null)}

特色:

一、全局隊列的工做特性跟併發隊列一致。 實際上,全局隊列就是系統爲了方便程序員,專門提供的一種特殊的併發隊列。

二、全局隊列和併發隊列的區別:

* 全局隊列沒有名稱,不管ARC仍是MRC都不須要考慮內存釋放,平常開發,建議使用全局隊列
* 併發隊列有名稱,若是在MRC開發中,須要使用dispatch_release來釋放相應的對象,dispatch_barrier 必須使用自定義的併發隊列,開發第三方框架,建議使用併發隊列

三、函數

dispatch_get_global_queue(long identifier, unsigned long flags);

這個函數中有兩個參數:
第一個參數: identifier
iOS7.0,表示的是優先級:
DISPATCH_QUEUE_PRIORITY_HIGH = 2; 高優先級
DISPATCH_QUEUE_PRIORITY_DEFAULT = 0; 默認優先級
DISPATCH_QUEUE_PRIORITY_LOW = -2; 低優先級
DISPATCH_QUEUE_PRIORITY_BACKGROUND = INT16_MIN; 後臺優先級
iOS8.0開始,推薦使用服務質量(QOS):
QOS_CLASS_USER_INTERACTIVE = 0x21; 用戶交互
QOS_CLASS_USER_INITIATED = 0x19; 用戶指望
QOS_CLASS_DEFAULT = 0x15; 默認
QOS_CLASS_UTILITY = 0x11; 實用工具
QOS_CLASS_BACKGROUND = 0x09; 後臺
QOS_CLASS_UNSPECIFIED = 0x00; 未指定
經過對比可知: 第一個參數傳入0,能夠同時適配iOS7及iOS7之後的版本。
服務質量和優先級是一一對應的:
DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED
DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY
DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
第二個參數: flags
爲將來保留使用的,始終傳入0。
Reserved for future use.

七、GCD總結

一、開不開線程,由執行任務的函數決定

  • 同步執行不開線程
  • 異步執行開線程

二、異步執行任務,開幾條線程由隊列決定

  • 串行隊列,只會開一條線程,由於一條就足夠了
  • 併發隊列,能夠開多條線程,具體開幾條由線程池決定

對主隊列而言,無論是同步執行仍是異步執行,都不會開線程。

最後盜圖總結一張

1


6、NSOperation

一、NSOperation簡介

1.1 NSOperation與GCD的區別:

  • OC語言中基於 GCD 的面向對象的封裝;
  • 使用起來比 GCD 更加簡單;
  • 提供了一些用 GCD 很差實現的功能;
  • 蘋果推薦使用,使用 NSOperation 程序員不用關心線程的生命週期

1.2NSOperation的特色

  • NSOperation 是一個抽象類,抽象類不能直接使用,必須使用它的子類
  • 抽象類的用處是定義子類共有的屬性和方法

二、核心概念

將操做添加到隊列,異步執行。相對於GCD建立任務,將任務添加到隊列。

將NSOperation添加到NSOperationQueue就能夠實現多線程編程

三、操做步驟

  • 先將須要執行的操做封裝到一個NSOperation對象中
  • 而後將NSOperation對象添加到NSOperationQueue中
  • 系統會自動將NSOperationQueue中的NSOperation取出來
  • 將取出的NSOperation封裝的操做放到一條新線程中執行

四、NSInvocationOperation

No1.

代碼

- (void)viewDidLoad {
[super viewDidLoad];
//建立操做,而後調用操做的start方法
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil];
NSLog(@"%d",op.isFinished);
[op start];
NSLog(@"%d",op.isFinished);
}

- (void)demo {
NSLog(@"hello %@",[NSThread currentThread]);
}

打印輸出

2016-02-25 22:12:30.054 test[892:834660] 0
2016-02-25 22:12:30.054 test[892:834660] hello <NSThread: 0x7fad12704f80>{number = 1, name = main}
2016-02-25 22:12:30.054 test[892:834660] 1

結論:[op start]在主線程中調用的,因此執行的線程也會是在主線程執行! 重複調用start也只會執行一次,由於NSOperation會有一個屬性去記住,是否已經完成了該操做!

No2.

代碼

- (void)viewDidLoad {
[super viewDidLoad];
//    建立操做,將操做添加到NSOperationQueue中,而後就會異步的自動執行
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil];
//隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//把操做添加到隊列
[queue addOperation:op];
}

- (void)demo {
NSLog(@"hello %@",[NSThread currentThread]);
}

打印

2016-02-25 22:21:44.999 test[912:842412] hello <NSThread: 0x7fab92610080>{number = 2, name = (null)}

將操做添加到NSOperationQueue中,而後就會異步的自動執行

五、NSBlockOperation

NSBlockOperation 中使用block的方式讓全部代碼邏輯在一塊兒,使用起來更加簡便。

NO1.

代碼

//建立操做
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"hello %@",[NSThread currentThread]);
}];
//更新op的狀態,執行main方法,不會開新線程
[op start];

輸出

2016-02-25 22:25:30.442 test[923:846208] hello <NSThread: 0x7fd410d055a0>{number = 1, name = main}

NO2.

代碼

//    建立隊列,建立操做,將操做添加到隊列中執行
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"hello %@",[NSThread currentThread]);
}];
[queue addOperation:op];

輸出
2016-02-25 22:26:48.064 test[934:848038] hello <NSThread: 0x7fc6bbb24c80>{number = 2, name = (null)}

NO3.

代碼

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

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

輸出

2016-02-25 22:27:56.445 test[945:850128] hello <NSThread: 0x7f98dbc2cae0>{number = 2, name = (null)}

建立隊列,添加block形式的操做


7、案例

線程之間的通訊問題

技術方案:NSOperation

[self.queue addOperationWithBlock:^{
    NSLog(@"異步下載圖片");
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        NSLog(@"回到主線程,更新UI");
    }];
}];

技術方案:GCD

dispatch_async(dispatch_get_global_queue(0, 0), ^{
      NSLog(@"下載圖片---%@",[NSThread currentThread]);
    dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主線程刷新圖片的顯示 -%@",[NSThread currentThread]);
        });
    });

這文章寫了很久,,過年一直到如今,終於寫完。。。

轉載請註明來自吃飯睡覺擼碼的博客 http://www.cnblogs.com/Erma-king/,幷包含相關連接。

相關文章
相關標籤/搜索