多線程GCD和NSOperation

目錄

一、基本概念

  • 1.多線程
  • 2.串行和并行, 并發(fā)
  • 3.隊列與任務(wù)
  • 4.同步與異步
  • 5.線程狀態(tài)
  • 6.多線程方案

二、GCD

  • 1.GCD簡介
  • 2.GCD的優(yōu)勢
  • 3.GCD任務(wù)和隊列
  • 4.任務(wù)的管理(調(diào)度)
  • 5.任務(wù)的執(zhí)行
  • 6.同步,異步,并發(fā),串行的組合
  • 7.GCD其他函數(shù)
  • 7.1延遲執(zhí)行
  • 7.2 一次性代碼
  • 7.3 隊列組
  • 7.4 快速迭代dispatch_apply函數(shù)
  • 7.5.dispatch_barrier_async
  • 7.6.補充

三、NSOperation和NSOperationQueue

  • 1.NSOperation
    • 1.1NSInvocationOperation
    • 1.2.NSBlockOperation
    • 1.3.自定義NSOperation
    • 1.4.操作之間的關(guān)系 -- 操作依賴
    • 1.5.操作的監(jiān)聽(KVO)
  • 2.NSOperationQueue
    • 2.1使用
    • 2.2設(shè)置最大并發(fā)數(shù)
    • 2.3隊列的取消,暫停和恢復(fù)

四、補充

  • 1.GCD和NSOperation區(qū)別
  • 2.線程間的通信
  • 3.線程安全
  • 4.熟悉SDWebImage的實現(xiàn)原理

一、基本概念


1.多線程

  • 什么是多線程?

    進程是資源分配的最小單位,線程是CPU調(diào)度的最小單位。
    多線程:是指從軟件或者硬件上實現(xiàn)多個線程的并發(fā)技術(shù)。

    
    試想一下,一個任務(wù)由十個子任務(wù)組成。現(xiàn)在有兩種方式完成這個任務(wù)
    
    1.建十個線程,把每個子任務(wù)放在對應(yīng)的線程中執(zhí)行。 
    
    2.把十個任務(wù)放在一個線程里,按順序執(zhí)行。
    
    效率后者完勝前者 ?
    

    了解了多線程的原理后,你會有更深的理解

  • 多線程的原理

    • 1個線程中任務(wù)的執(zhí)行是串行的

      • 如果要在1個線程中執(zhí)行多個任務(wù),那么只能一個一個地按順序執(zhí)行這些任,也就是說,在同一時間內(nèi),1個線程只能執(zhí)行1個任務(wù)
    • 1個進程中可以開啟多條線程,每條線程可以并行(同時)執(zhí)行不同的任務(wù)

      對于單核

      • 同一時間,單核CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)
      • 多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間調(diào)度(切換),如果CPU調(diào)度線程的時間足夠快,就造成了多線程并發(fā)執(zhí)行的假象

      對于多核

      • 多線程并發(fā)(同時)執(zhí)行,就是多條線程同時執(zhí)行任務(wù)

      并不是所有的框架都支持多線程, 必須要有多核的cpu支持才行, 單核cpu即使開了多線程運行速度也不會有變化,過多的線程會占用大量的內(nèi)存,目前主線程和子線程的開銷均為512kb

  • 多線程的好處:
    (1)使用多線程可以把程序中占據(jù)時間長的任務(wù)放到后臺去處理,如圖片、視屏的下載
    (2)發(fā)揮多核處理器的優(yōu)勢,并發(fā)執(zhí)行讓系統(tǒng)運行的更快、更流暢,用戶體驗更好(多核)

  • 多線程的缺點:
    (1)大量的線程降低代碼的可讀性;
    (2)更多的線程需要更多的內(nèi)存空間
    (3)當(dāng)多個線程對同一個資源出現(xiàn)爭奪時候要注意線程安全的問題

2.串行和并行, 并發(fā)

并行指的是一種技術(shù),一個同時處理多個任務(wù)的技術(shù)。它描述了一種能夠同時處理多個任務(wù)的能力,側(cè)重點在于“運行”。

并行的反義詞就是串行,表示任務(wù)必須按順序來,一個一個執(zhí)行,前一個執(zhí)行完了才能執(zhí)行后一個。

并發(fā)指的是一種現(xiàn)象,一種經(jīng)常出現(xiàn),無可避免的現(xiàn)象。它描述的是“多個任務(wù)同時發(fā)生,需要被處理”這一現(xiàn)象。它的側(cè)重點在于“發(fā)生”。

  • 比如在一個景點,一個游客買票對應(yīng)售票處來說,當(dāng)做一個任務(wù),而很多游客需要買票,同時發(fā)生,需要被處理,這種現(xiàn)象稱為并發(fā)

  • 景點開放了多個買票窗口,同一時間內(nèi)能服務(wù)多個游客。這種情況可以理解為并行

  • 進入景點的控制門,一次只能一個人通過,這叫串行;

    我們經(jīng)常掛在嘴邊的“多線程”,正是采用了并行技術(shù),從而提高了執(zhí)行效率。因為有多個線程,所以計算機的多個CPU可以同時工作,同時處理不同線程內(nèi)的指令。

3.隊列與任務(wù)

隊列只是負責(zé)任務(wù)的調(diào)度,而不負責(zé)任務(wù)的執(zhí)行, 任務(wù)是在線程中執(zhí)行的

  • 隊列的特點:先進先出,排在前面的任務(wù)最先調(diào)度

  • 串行隊列:任務(wù)按照順序被調(diào)度,前一個任務(wù)不執(zhí)行完畢,隊列不會調(diào)度

  • 并行隊列:只要有空閑的線程,隊列就會調(diào)度當(dāng)前任務(wù),交給線程去執(zhí)行,不需要考慮前面是都有任務(wù)在執(zhí)行,只要有線程可以利用,隊列就會調(diào)度任務(wù)。

不管是串行隊列(SerialQueue)還是并行隊列(ConcurrencyQueue),都是FIFO隊列。也就意味著,任務(wù)一定是一個一個地,按照先進先出的順序來調(diào)度。

4.同步與異步

同步(sync) 和 異步(async) 的主要區(qū)別在于會不會阻塞當(dāng)前線程,直到 Block 中的任務(wù)執(zhí)行完畢!
如果是 同步(sync) 操作,它會阻塞當(dāng)前線程并等待 Block 中的任務(wù)執(zhí)行完畢,然后當(dāng)前線程才會繼續(xù)往下運行。
如果是 異步(async)操作,當(dāng)前線程會直接往下執(zhí)行,它不會阻塞當(dāng)前線程。

同步和異步關(guān)注的是消息通信機制 (synchronous communication/ asynchronous communication)

所謂同步,就是在發(fā)出一個調(diào)用時,在沒有得到結(jié)果之前,該調(diào)用就不返回。但是一旦調(diào)用返回,就得到返回值了。

換句話說,就是由調(diào)用者主動等待這個調(diào)用的結(jié)果。

而異步則是相反,調(diào)用在發(fā)出之后,這個調(diào)用就直接返回了,所以沒有返回結(jié)果。換句話說,當(dāng)一個異步過程調(diào)用發(fā)出后,調(diào)用者不會立刻得到結(jié)果。而是在調(diào)用發(fā)出后,被調(diào)用者通過狀態(tài)、通知來通知調(diào)用者,或通過回調(diào)函數(shù)處理這個調(diào)用。

關(guān)于同步異步、串行并行和線程的關(guān)系,如下表格所示

同步 異步
主隊列 在主線程中執(zhí)行 在主線程中執(zhí)行
串行隊列 在當(dāng)前線程中執(zhí)行 新建線程執(zhí)行
并發(fā)隊列 在當(dāng)前線程中執(zhí)行 新建線程執(zhí)行

5.線程狀態(tài)

一般來說,線程有五個狀態(tài)

  • 新建狀態(tài):線程通過各種方式在被創(chuàng)建之初,還沒有調(diào)用開始執(zhí)行方法,這個時候的線程就是新建狀態(tài);
    self.thread=[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
  • 就緒狀態(tài):在新建線程被創(chuàng)建之后調(diào)用了開始執(zhí)行方法[thread start],但是CPU并不是真正的同時執(zhí)行多個任務(wù),所以要等待CPU調(diào)用,這個時候線程處于就緒狀態(tài)。處于就緒狀態(tài)的線程并不一定立即執(zhí)行線程里的代碼,線程還必須同其他線程競爭CPU時間,只有獲得CPU時間才可以運行線程。
  • 運行狀態(tài):線程獲得CPU時間,被CPU調(diào)用之后就進入運行狀態(tài)
  • CPU 負責(zé)調(diào)度可調(diào)度線程池中線程的執(zhí)行
  • 線程執(zhí)行完成之前(死亡之前),狀態(tài)可能會在就緒和運行之間來回切換
  • 就緒和運行之間的狀態(tài)變化由 CPU 負責(zé),程序員不能干預(yù)
  • 阻塞狀態(tài):所謂阻塞狀態(tài)是正在運行的線程沒有運行結(jié)束,暫時讓出CPU,這時其他處于就緒狀態(tài)的線程就可以獲得CPU時間,進入運行狀態(tài)。
  • 線程通過調(diào)用sleep方法進入睡眠狀態(tài)[NSThread sleepForTimeInterval:2.0];
  • 線程調(diào)用一個在I/O上被阻塞的操作,即該操作在輸入輸出操作完成之前不會返回到它的調(diào)用者
  • 線程試圖得到一個鎖,而該鎖正被其他線程持有;
  • 線程在等待某個觸發(fā)條件
  • 死亡狀態(tài):當(dāng)線程的任務(wù)結(jié)束,發(fā)生異常,或者是強制退出這三種情況會導(dǎo)致線程的死亡。線程死亡后,線程對象從內(nèi)存中移除。一旦線程進入死亡狀態(tài),再次嘗試重新開啟線程,則程序會掛。
@implementation ViewController
- (void)viewDidLoad {
  [super viewDidLoad];
    // 1.創(chuàng)建線程--->新建狀態(tài)
   self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(therad) object:nil];
    // 設(shè)置線程名稱
   self.thread.name = @"線程A";
 }

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  // 2.開啟線程---->就緒和運行狀態(tài)
  [self.thread start];
}

-(void)therad {
  NSThread *current=[NSThread currentThread];
  NSLog(@"therad---打印線程---%@",self.thread.name);
  NSLog(@"therad---線程開始---%@",current.name);
  //3.設(shè)置線程阻塞1,阻塞2秒 --->阻塞狀態(tài)
  NSLog(@"接下來,線程阻塞2秒");
  [NSThread sleepForTimeInterval:2.0];

  //第二種設(shè)置線程阻塞2,以當(dāng)前時間為基準(zhǔn)阻塞4秒
  NSLog(@"接下來,線程阻塞4秒");
  NSDate *date=[NSDate dateWithTimeIntervalSinceNow:4.0];
  [NSThread sleepUntilDate:date];
    for (int i=0; i<10; i++) {
         NSLog(@"線程--%d--%@",i,current.name);
        // 結(jié)束線程
        [NSThread exit];

  }
// 4. 任務(wù)結(jié)束 --- >死亡狀態(tài)
 NSLog(@"test---線程結(jié)束---%@",current.name);
}
@ end

線程的狀態(tài)demo

6.多線程方案

|多線程實現(xiàn)方案| 特點 | 語言|頻率|線程生命周期|
|:------|:----:| :-:|
|pthread | 1、一套通用的多線程API 2、適用于Unix\Linux\Windows等系統(tǒng) 3、跨平臺\可移植 4、使用難度大 | c語言 |幾乎不用|由程序員進行管理|
|NSThread | 1、使用更加面向?qū)ο?2、簡單易用,可直接操作線程對象 | OC語言 |偶爾使用|由程序員進行管理|
|GCD | 1、旨在替代NSThread等線程技術(shù) 2、充分利用設(shè)備的多核(自動) | c語言 |經(jīng)常使用|自動管理|
|NSOperation | 1、基于GCD 2、比GCD多了一些更簡單實用的功能,使用更加面向?qū)ο?| OC語言 |經(jīng)常使用|自動管理|

二、GCD


1. GCD簡介

全稱是Grand Central Dispatch,可譯為“牛逼的中樞調(diào)度器”

純C語言,提供了非常多強大的函數(shù)

2.GCD的優(yōu)勢

GCD是蘋果公司為多核的并行運算提出的解決方案

GCD會自動利用更多的CPU內(nèi)核(比如雙核、四核)

GCD會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)

程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼

3.任務(wù)和隊列

GCD有兩大重要概念,分別是隊列和任務(wù)

隊列:這里的隊列指任務(wù)隊列,即用來存放任務(wù)的隊列。隊列是一種特殊的線性表,采用FIFO(先進先出)的原則,即新任務(wù)總是被插入到隊列的末尾,而讀取任務(wù)的時候總是從隊列的頭部開始讀取。每讀取一個任務(wù),則從隊列中釋放一個任務(wù)

任務(wù):就是執(zhí)行操作的意思,換句話說就是你在線程中執(zhí)行的那段代碼。在GCD中是放在block中的。

4.任務(wù)的管理(調(diào)度)

在GCD中有兩種隊列:串行隊列和并發(fā)隊列,負責(zé)任務(wù)的管理

注意:隊列只負責(zé)任務(wù)的調(diào)度,不負責(zé)任務(wù)的執(zhí)行-----這是理解不同函數(shù),不同隊列,任務(wù)是否在新的線程執(zhí)行的關(guān)鍵

  • 并發(fā)隊列(Concurrent Dispatch Queue):只要有空閑的線程,隊列就會調(diào)度當(dāng)前任務(wù),交給線程去執(zhí)行,不需要考慮前面是都有任務(wù)在執(zhí)行,只要有線程可以利用,隊列就會調(diào)度任務(wù)。

    // 并發(fā)隊列的創(chuàng)建方法
    dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 使用全局并發(fā)隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    

    并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效,在同步函數(shù)下失去了任務(wù)調(diào)度的并發(fā)能力

  • 串行隊列(Serial Dispatch Queue):任務(wù)按照順序被調(diào)度,前一個任務(wù)不執(zhí)行完畢,隊列不會調(diào)度,這樣任務(wù),都是一個接著一個地執(zhí)行(一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))

    // 串行隊列的創(chuàng)建方法
    dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    
    // 使用主隊列(跟主線程相關(guān)聯(lián)的隊列)
    // 主隊列是GCD自帶的一種特殊的串行隊列,放在主隊列中的任務(wù),都會放到主線程中執(zhí)行
    dispatch_queue_t queue = dispatch_get_main_queue();
    

5.任務(wù)的執(zhí)行

  • (1)用同步的方式執(zhí)行任務(wù) dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

參數(shù)說明:
queue:隊列
block:任務(wù)

  • (2)用異步的方式執(zhí)行任務(wù) dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

任務(wù)的執(zhí)行方式是由函數(shù)決定的(async和sync),任務(wù)是封裝在block里。

// 同步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_sync(queue, ^{
  NSLog(@"%@",[NSThread currentThread]);    // 這里放任務(wù)代碼
});

// 異步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_async(queue, ^{
  NSLog(@"%@",[NSThread currentThread]);    // 這里放任務(wù)代碼
});

6.同步,異步,并發(fā),串行的組合

sync和async:

同步函數(shù)和異步函數(shù):負責(zé)任務(wù)的執(zhí)行,決定了任務(wù)的執(zhí)行方式。
同步函數(shù)(sync):只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步執(zhí)行(async):可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力

serial和concurrent:

串行隊列和并行隊列:只負責(zé)任務(wù)的調(diào)度,不負責(zé)任務(wù)的執(zhí)行
串行隊列(Serial Dispatch Queue):任務(wù)按照順序被調(diào)度,前一個任務(wù)不執(zhí)行完畢,隊列不會調(diào)度,這樣任務(wù),都是一個接著一個地執(zhí)行(一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))
并發(fā)隊列(Concurrent Dispatch Queue):只要有空閑的線程,隊列就會調(diào)度當(dāng)前任務(wù),交給線程去執(zhí)行,不需要考慮前面是都有任務(wù)在執(zhí)行,只要有線程可以利用,隊列就會調(diào)度任務(wù)。

各種組合的效果

并發(fā)隊列 創(chuàng)建串行隊列 主隊列
同步(sync) 1、沒有開啟線程 2、串行執(zhí)行任務(wù) 1、沒有開啟線程 2、串行執(zhí)行任務(wù) 1、沒有開啟線程 2、串行執(zhí)行任務(wù)
異步(async) 1、有開啟線程 2、并發(fā)執(zhí)行任務(wù) 1、有開啟線程 2、串行執(zhí)行任務(wù) 1、沒有開啟線程 2、串行執(zhí)行任務(wù)

代碼示范:

  • (1)用異步函數(shù)往并發(fā)隊列中添加任務(wù)
/**
 并發(fā)隊列+異步函數(shù)
 */
- (void)concurrentQueueInAsyn {
    //1.獲得全局的并發(fā)隊列
    dispatch_queue_t queue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //2.添加任務(wù)到隊列中,就可以執(zhí)行任務(wù)
    //異步函數(shù):具備開啟新線程的能力
    dispatch_async(queue, ^{
        NSLog(@"下載圖片1----%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"下載圖片2----%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"下載圖片3----%@",[NSThread currentThread]);
    });
    //打印主線程
    NSLog(@"主線程----%@",[NSThread mainThread]);
}

打印結(jié)果:同時開啟三個子線程

2017-04-17 16:30:45.518 GCD任務(wù)的執(zhí)行[1343:148362] 主線程----<NSThread: 0x6100000696c0>{number = 1, name = main}
2017-04-17 16:30:45.518 GCD任務(wù)的執(zhí)行[1343:148619] 下載圖片2----<NSThread: 0x608000070200>{number = 4, name = (null)}
2017-04-17 16:30:45.518 GCD任務(wù)的執(zhí)行[1343:148618] 下載圖片1----<NSThread: 0x60800006cb40>{number = 3, name = (null)}
2017-04-17 16:30:45.518 GCD任務(wù)的執(zhí)行[1343:148650] 下載圖片3----<NSThread: 0x61000006e000>{number = 5, name = (null)}
  • (2)用異步函數(shù)往串行隊列中添加任務(wù)
/**
 串行隊列+異步函數(shù)
 */
- (void)serialQueueInAsyn {
    //打印主線程
    NSLog(@"主線程----%@",[NSThread mainThread]);
    
    //1.創(chuàng)建串行隊列
    dispatch_queue_t  queue= dispatch_queue_create("11", NULL);
    //第一個參數(shù)為串行隊列的名稱,是c語言的字符串
    //第二個參數(shù)為隊列的屬性,一般來說串行隊列不需要賦值任何屬性,所以通常傳空值(NULL)
    
    //2.添加任務(wù)到隊列中執(zhí)行
    dispatch_async(queue, ^{
        NSLog(@"下載圖片1----%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"下載圖片2----%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"下載圖片3----%@",[NSThread currentThread]);
    });
    
    //3.釋放資源
    //dispatch_release(queue);
}

打印結(jié)果:會開啟線程,但是只開啟一個線程

2017-04-17 16:41:02.665 GCD任務(wù)的執(zhí)行[1359:153689] 主線程----<NSThread: 0x610000069a80>{number = 1, name = main}
2017-04-17 16:41:02.665 GCD任務(wù)的執(zhí)行[1359:153728] 下載圖片1----<NSThread: 0x610000072ec0>{number = 3, name = (null)}
2017-04-17 16:41:02.705 GCD任務(wù)的執(zhí)行[1359:153728] 下載圖片2----<NSThread: 0x610000072ec0>{number = 3, name = (null)}
2017-04-17 16:41:02.705 GCD任務(wù)的執(zhí)行[1359:153728] 下載圖片3----<NSThread: 0x610000072ec0>{number = 3, name = (null)}
  • (3)用同步函數(shù)往并發(fā)隊列中添加任務(wù)
/**
 異步隊列+同步函數(shù)
 */
- (void)concurrentQueueInSyn {
    //打印主線程
    NSLog(@"主線程----%@",[NSThread mainThread]);
    
    //1.創(chuàng)建并行隊列
    dispatch_queue_t  queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    
    //2.添加任務(wù)到隊列中執(zhí)行
    dispatch_sync(queue, ^{
        NSLog(@"下載圖片1----%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"下載圖片2----%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"下載圖片3----%@",[NSThread currentThread]);
    });
}

打印結(jié)果:不會開啟新的線程,并發(fā)隊列失去了并發(fā)的功能

2017-04-17 16:47:17.868 GCD任務(wù)的執(zhí)行[1374:157934] 主線程----<NSThread: 0x618000070e80>{number = 1, name = main}
2017-04-17 16:47:17.868 GCD任務(wù)的執(zhí)行[1374:157934] 下載圖片1----<NSThread: 0x618000070e80>{number = 1, name = main}
2017-04-17 16:47:17.868 GCD任務(wù)的執(zhí)行[1374:157934] 下載圖片2----<NSThread: 0x618000070e80>{number = 1, name = main}
2017-04-17 16:47:17.868 GCD任務(wù)的執(zhí)行[1374:157934] 下載圖片3----<NSThread: 0x618000070e80>{number = 1, name = main}
  • (4)用同步函數(shù)往串行隊列中添加任務(wù)
/**
 串行隊列+同步函數(shù)
 */
- (void)serialQueueInSyn {
    NSLog(@"用同步函數(shù)往串行隊列中添加任務(wù)");
    //打印主線程
    NSLog(@"主線程----%@",[NSThread mainThread]);
    
    //創(chuàng)建串行隊列
    dispatch_queue_t  queue= dispatch_queue_create("11", NULL);
    
    //2.添加任務(wù)到隊列中執(zhí)行
    dispatch_sync(queue, ^{
        NSLog(@"下載圖片1----%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"下載圖片2----%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"下載圖片3----%@",[NSThread currentThread]);
    });
}

打印結(jié)果:不會開啟新的線程

2017-04-17 16:50:19.439 GCD任務(wù)的執(zhí)行[1388:160753] 主線程----<NSThread: 0x610000065600>{number = 1, name = main}
2017-04-17 16:50:19.483 GCD任務(wù)的執(zhí)行[1388:160753] 下載圖片1----<NSThread: 0x610000065600>{number = 1, name = main}
2017-04-17 16:50:19.483 GCD任務(wù)的執(zhí)行[1388:160753] 下載圖片2----<NSThread: 0x610000065600>{number = 1, name = main}
2017-04-17 16:50:19.483 GCD任務(wù)的執(zhí)行[1388:160753] 下載圖片3----<NSThread: 0x610000065600>{number = 1, name = main}

demo地址

7.GCD其他函數(shù)

7.1 延遲執(zhí)行

在3s后,可能不僅限于3s,執(zhí)行處理,因為Main Dispatch Queue 在主線程的Runloop中執(zhí)行,所以在比如每隔1/60秒執(zhí)行的Runloop中,Block最快在3s后執(zhí)行,最慢在3s+1/60s后執(zhí)行,并且在Main Dispatch Queue有大量處理追加或者主線程的處理本身有延遲時,這個時間會更長,所以有嚴(yán)格時間的要求下使用時會出現(xiàn)問題

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // to do
    });

7.2 一次性代碼

使用dispatch_once函數(shù)能保證某段代碼在程序運行過程中只被執(zhí)行1次

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

    // 只執(zhí)行1次的代碼(這里面默認是線程安全的)

});

整個程序運行過程中,只會執(zhí)行一次。

7.3 隊列組

有這么1種需求:

首先:分別異步執(zhí)行2個耗時的操作

其次:等2個異步操作都執(zhí)行完畢后,再回到主線程執(zhí)行操作

如果想要快速高效地實現(xiàn)上述需求,可以考慮用隊列組

dispatch_group_t group =  dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 執(zhí)行1個耗時的異步操作

});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 執(zhí)行1個耗時的異步操作

});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

    // 等前面的異步操作都執(zhí)行完畢后,回到主線程...

});

** 需求:從網(wǎng)絡(luò)上下載兩張圖片,把兩張圖片合并成一張最終顯示在view上。**

  • 1.沒有使用group的方法
/**
 沒有使用group的方法
 */
- (void)nonGroup {    
    dispatch_async(global_quque, ^{
        //下載圖片1
        UIImage *image1= [self imageWithUrl:self.imageString1];
        NSLog(@"圖片1下載完成---%@",[NSThread currentThread]);
        
        //下載圖片2
        UIImage *image2= [self imageWithUrl:self.imageString2];
        NSLog(@"圖片2下載完成---%@",[NSThread currentThread]);
        
        //回到主線程顯示圖片
        dispatch_async(main_queue, ^{
            NSLog(@"顯示圖片---%@",[NSThread currentThread]);
            self.imageView1.image=image1;
            self.imageView2.image=image2;
            //合并兩張圖片
            UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);
            [image1 drawInRect:CGRectMake(0, 0, 100, 100)];
            [image2 drawInRect:CGRectMake(100, 0, 100, 100)];
            self.imageView3.image=UIGraphicsGetImageFromCurrentImageContext();
            //關(guān)閉上下文
            UIGraphicsEndImageContext();
            NSLog(@"圖片合并完成---%@",[NSThread currentThread]);
        });
    });
}

//封裝一個方法,傳入一個url參數(shù),返回一張網(wǎng)絡(luò)上下載的圖片
- (UIImage *)imageWithUrl:(NSString *)urlStr {
    NSURL *url=[NSURL URLWithString:urlStr];
    NSData *data=[NSData dataWithContentsOfURL:url];
    UIImage *image=[UIImage imageWithData:data];
    return image;
}

打印結(jié)果:這種方式的效率不高,需要等到圖片1.圖片2都下載完成后才行。

2017-04-17 18:14:05.716 dispatch_group_asyn[1573:207552] 圖片1下載完成---<NSThread: 0x61800006b140>{number = 5, name = (null)}
2017-04-17 18:14:05.795 dispatch_group_asyn[1573:207552] 圖片2下載完成---<NSThread: 0x61800006b140>{number = 5, name = (null)}
2017-04-17 18:14:05.795 dispatch_group_asyn[1573:206907] 顯示圖片---<NSThread: 0x610000064ac0>{number = 1, name = main}
2017-04-17 18:14:05.802 dispatch_group_asyn[1573:206907] 圖片合并完成---<NSThread: 0x610000064ac0>{number = 1, name = main}

  • 2.使用group的方法
- (void)group {
    //1.創(chuàng)建一個隊列組
    dispatch_group_t group = dispatch_group_create();
    
    //2.開啟一個任務(wù)下載圖片1
    __block UIImage *image1=nil;
    dispatch_group_async(group, global_quque, ^{
        image1= [self imageWithUrl:@"http://d.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=2b9a12172df5e0fefa1581533d095fcd/cefc1e178a82b9019115de3d738da9773912ef00.jpg"];
        NSLog(@"圖片1下載完成---%@",[NSThread currentThread]);
    });
    
    //3.開啟一個任務(wù)下載圖片2
    __block UIImage *image2=nil;
    dispatch_group_async(group, global_quque, ^{
        image2= [self imageWithUrl:@"http://h.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=f47fd63ca41ea8d39e2f7c56f6635b2b/1e30e924b899a9018b8d3ab11f950a7b0308f5f9.jpg"];
        NSLog(@"圖片2下載完成---%@",[NSThread currentThread]);
    });
    
    //同時執(zhí)行下載圖片1\下載圖片2操作
    
    //4.等group中的所有任務(wù)都執(zhí)行完畢, 再回到主線程執(zhí)行其他操作
    dispatch_group_notify(group,main_queue, ^{
        NSLog(@"顯示圖片---%@",[NSThread currentThread]);
        self.imageView1.image=image1;
        self.imageView2.image=image2;
        
        //合并兩張圖片
        //注意最后一個參數(shù)是浮點數(shù)(0.0),不要寫成0。
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);
        [image1 drawInRect:CGRectMake(0, 0, 100, 100)];
        [image2 drawInRect:CGRectMake(100, 0, 100, 100)];
        self.imageView3.image=UIGraphicsGetImageFromCurrentImageContext();
        //關(guān)閉上下文
        UIGraphicsEndImageContext();
        
        NSLog(@"圖片合并完成---%@",[NSThread currentThread]);
    });
}


打印結(jié)果:同時開啟了兩個子線程,分別下載圖片

2017-04-17 18:18:07.222 dispatch_group_asyn[1586:210326] 圖片2下載完成---<NSThread: 0x610000077280>{number = 4, name = (null)}
2017-04-17 18:18:07.271 dispatch_group_asyn[1586:210307] 圖片1下載完成---<NSThread: 0x610000077e80>{number = 5, name = (null)}
2017-04-17 18:18:07.271 dispatch_group_asyn[1586:210270] 顯示圖片---<NSThread: 0x618000073240>{number = 1, name = main}
2017-04-17 18:18:07.278 dispatch_group_asyn[1586:210270] 圖片合并完成---<NSThread: 0x618000073240>{number = 1, name = main}

groupDemo

7.4 快速迭代dispatch_apply函數(shù)

  • 普通迭代
/**
 * 普通迭代
 */
- (void)commonIteration {
    for (int i = 0; i < 9; i++) {
        NSLog(@"%zd----%@", i, [NSThread currentThread]);
    }
}

打印結(jié)果:速度慢

2017-04-17 23:10:26.475 dispatch_apply函數(shù)[575:8754] 0----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.475 dispatch_apply函數(shù)[575:8754] 1----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.475 dispatch_apply函數(shù)[575:8754] 2----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.476 dispatch_apply函數(shù)[575:8754] 3----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.476 dispatch_apply函數(shù)[575:8754] 4----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.476 dispatch_apply函數(shù)[575:8754] 5----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.476 dispatch_apply函數(shù)[575:8754] 6----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.476 dispatch_apply函數(shù)[575:8754] 7----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.477 dispatch_apply函數(shù)[575:8754] 8----<NSThread: 0x608000079b40>{number = 1, name = main}

  • 多線程快速迭代
/**
 * 快速迭代(應(yīng)用:拷貝文件)
 */
- (void)quickIteration {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(9, queue, ^(size_t index) {
         NSLog(@"%zd----%@", index, [NSThread currentThread]);
    });
}

打印結(jié)果:創(chuàng)建多條線程,進行迭代,速度快

2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11278] 0----<NSThread: 0x600000261240>{number = 1, name = main}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11318] 3----<NSThread: 0x608000267040>{number = 5, name = (null)}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11335] 1----<NSThread: 0x600000269f40>{number = 3, name = (null)}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11315] 2----<NSThread: 0x6080002668c0>{number = 4, name = (null)}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11278] 4----<NSThread: 0x600000261240>{number = 1, name = main}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11318] 5----<NSThread: 0x608000267040>{number = 5, name = (null)}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11335] 6----<NSThread: 0x600000269f40>{number = 3, name = (null)}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11315] 7----<NSThread: 0x6080002668c0>{number = 4, name = (null)}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11278] 8----<NSThread: 0x600000261240>{number = 1, name = main}

7.5.dispatch_barrier_async

在訪問數(shù)據(jù)庫或文件時,使用Serial Dispatch Queue 可避免數(shù)據(jù)競爭的問題。

寫入處理確實不可與其他的寫入處理以及包含讀取處理的其他某些處理并行執(zhí)行。但是如果讀取處理只是讀取處理并行執(zhí)行,那么多個并行執(zhí)行就不會發(fā)生問題。

也就是說,為了高效率的進行訪問,讀取處理追加到Concurrent Dispatch Queue中,寫入處理在任何一個讀取處理沒有執(zhí)行的狀態(tài)下,追加到Serial Dispatch Queue中即可(在寫入處理之前,讀取處理不可執(zhí)行)

 /**
 并發(fā)隊列+異步函數(shù)  讀1和讀2之間寫入數(shù)據(jù)
 */
- (void)concurrentQueueInAsync {
    dispatch_queue_t queue = dispatch_queue_create("JJ", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"block for reading 0");
    });
    dispatch_async(queue, ^{
        NSLog(@"block for reading 1");
    });
    // 在1和2之間執(zhí)行寫入處理,那么根據(jù)Concurrent Dispatch Queue的性質(zhì),就有可能在追加到寫入處理前面的處理中讀取到與期待不符的數(shù)據(jù)。
    dispatch_async(queue, ^{
        NSLog(@"block for writing");
    });
    dispatch_async(queue, ^{
        NSLog(@"block for reading 2");
    });
    dispatch_async(queue, ^{
        NSLog(@"block for reading 3");
    });
}

因此我們要使用dispatch_barrier_async函數(shù)。dispatch_barrier_async函數(shù)會等待追加到Concurrent Dispatch Queue上的并行執(zhí)行的處理全部結(jié)束之后,再將指定的處理追加到該Concurrent Dispatch Queue中,然后在由dispatch_barrier_async函數(shù)追加的處理執(zhí)行完畢后,Concurrent Dispatch Queue才恢復(fù)為一般的動作,追加到該Concurrent Dispatch Queue的處理又開始并行執(zhí)行

/**
    dispatch_barrier_async函數(shù)控制讀寫操作  讀1和讀2之間寫入數(shù)據(jù)
 */
- (void)dispatch_barrier_async {
    dispatch_queue_t queue = dispatch_queue_create("JJ", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"block for reading 0");
    });
    dispatch_async(queue, ^{
        NSLog(@"block for reading 1");
    });
    // 將這個block之前的任務(wù)攔著,等執(zhí)行完后,執(zhí)行這個block,然后再并行執(zhí)行其他任務(wù)。
    dispatch_barrier_async(queue, ^{
        NSLog(@"block for writing");
    });
    dispatch_async(queue, ^{
        NSLog(@"block for reading 2");
    });
    dispatch_async(queue, ^{
        NSLog(@"block for reading 3");
    });
}

使用Concurrent Dispatch Queue和dispatch_barrier_async函數(shù)可以實現(xiàn)高效率的數(shù)據(jù)庫訪問和文件訪問。
demo地址

7.6.補充

  • dispatch_sync函數(shù)
    將指定的block任務(wù)同步追加到指定的Dispatch Queue中,在追加Block結(jié)束之前,dispatch_sync函數(shù)會一直等待,產(chǎn)生死鎖問題

在主線程中執(zhí)行以下代碼會死鎖。

    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"block middle");
    });
a.dispatch_sync()函數(shù)在主線程中調(diào)用;

b.調(diào)用dispatch_sync()函數(shù)會立即阻塞調(diào)用時該函數(shù)所在的線程,即主線程等待dispatch_sync()函數(shù)返回

c.dispatch_sync()函數(shù)追加任務(wù)(即Block代碼塊)到主隊列dispatch_get_main_queue()中

d.主隊列是一種特殊的串行隊列,主隊列的任務(wù)在主線程中執(zhí)行,但此時主線程被阻塞,無法執(zhí)行Block代碼塊,導(dǎo)致dispatch_sync()函數(shù)無法返回,一直等待Block被主線程執(zhí)行,最終導(dǎo)致死鎖

也就是說,主線程等待dispatch_sync函數(shù)返回,而dispatch_sync函數(shù)將block添加到主線程,主線程因為要等dispatch_sync函數(shù)返回才去執(zhí)行block,主線程被阻塞,沒有機會去執(zhí)行block,發(fā)生死鎖。

解決上述問題的死鎖,也簡單,使用Global Dispatch Queue進行處理block任務(wù)
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync(queue, ^{
        NSLog(@"block middle");
    });
a.調(diào)用dispatch_sync()函數(shù)會立即阻塞調(diào)用時該函數(shù)所在的線程,全局并發(fā)線程等待dispatch_sync()函數(shù)返回

b.dispatch_sync()函數(shù)追加任務(wù)(即Block代碼塊)到并發(fā)隊列dispatch_get_global_queue()中

c.并發(fā)隊列dispatch_get_global_queue()并發(fā)執(zhí)行任務(wù),block不需等待,可以執(zhí)行,執(zhí)行完后,dispatch_sync函數(shù)返回

三、NSOperation和NSOperationQueue

  • NSOperation的作用:配合使用NSOperation和NSOperationQueue也能實現(xiàn)多線程編程
  • NSOperation和NSOperationQueue實現(xiàn)多線程的具體步驟
    • 先將需要執(zhí)行的操作封裝到一個NSOperation對象中
    • 然后將NSOperation對象添加到NSOperationQueue中
    • 系統(tǒng)會自動將NSOperationQueue中的NSOperation取出來,放到線程執(zhí)行

1.NSOperation

NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類:

  • NSInvocationOperation
  • NSBlockOperation
  • 自定義子類繼承NSOperation,實現(xiàn)內(nèi)部相應(yīng)的方法

1.1NSInvocationOperation

a.創(chuàng)建NSInvocationOperation對象

-(id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;

b.調(diào)用start方法開始執(zhí)行操作

- (void)start;

注意:默認情況下,調(diào)用了start方法后并不會開一條新線程去執(zhí)行操作,而是在當(dāng)前線程同步執(zhí)行操作,只有將NSOperation放到一個NSOperationQueue中,才會異步執(zhí)行操作

  • 代碼
/**
 NSInvocationOperation的使用
 */
- (void)invocationOperation {
    // 1.創(chuàng)建操作
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    // 2.啟動操作
    [op start];
}

- (void)run {
    NSLog(@"------%@", [NSThread currentThread]);
}
  • 打印結(jié)果:不開啟線程,在當(dāng)前線程執(zhí)行操作
2017-04-17 23:27:16.796 NSOperation子類的基本使用[845:19316] ------<NSThread: 0x600000076b00>{number = 1, name = main}

1.2.NSBlockOperation

a.創(chuàng)建NSBlockOperation對象

+(id)blockOperationWithBlock:(void (^)(void))block;

b.通過addExecutionBlock:方法添加更多的操作

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

c.調(diào)用start方法開始執(zhí)行操作

- (void)start;

注意:只要NSBlockOperation封裝的操作數(shù) > 1,就會異步執(zhí)行操作

  • 代碼
/**
 NSBlockOperation的基本使用
 */
- (void)blockOperation {
    // 1.創(chuàng)建操作
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        // 在主線程
        NSLog(@"下載1------%@", [NSThread currentThread]);
    }];
    
    // 2.添加額外的任務(wù)(在子線程執(zhí)行)
    [op addExecutionBlock:^{
        NSLog(@"下載2------%@", [NSThread currentThread]);
    }];
    
    [op addExecutionBlock:^{
        NSLog(@"下載3------%@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        NSLog(@"下載4------%@", [NSThread currentThread]);
    }];
    
    // 3.啟動操作
    [op start];
}
  • 打印結(jié)果:第一個任務(wù)在當(dāng)前當(dāng)前線程,后面添加的任務(wù)會放在新的線程里執(zhí)行
2017-04-17 23:30:54.619 NSOperation子類的基本使用[872:21229] 下載1------<NSThread: 0x6080000645c0>{number = 1, name = main}
2017-04-17 23:30:54.619 NSOperation子類的基本使用[872:21267] 下載3------<NSThread: 0x600000071c80>{number = 4, name = (null)}
2017-04-17 23:30:54.619 NSOperation子類的基本使用[872:21266] 下載2------<NSThread: 0x60800006da80>{number = 3, name = (null)}
2017-04-17 23:30:54.619 NSOperation子類的基本使用[872:21269] 下載4------<NSThread: 0x600000071dc0>{number = 5, name = (null)}

1.3.自定義NSOperation

a.自定義NSOperation的步驟
  • 繼承NSOperation
  • 重寫- (void)main方法,在里面實現(xiàn)想執(zhí)行的任務(wù)
  • 外部創(chuàng)建操作,并調(diào)用start方法
#import "CustomOperation.h"

@implementation CustomOperation

/**
 重新main方法,在這里執(zhí)行任務(wù)
 */
- (void)main {
    for (NSInteger i = 0; i<10; i++) {
        NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
    }
    if (self.isCancelled) return;
    
    for (NSInteger i = 0; i<10; i++) {
        NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
    }
    if (self.isCancelled) return;
    
    for (NSInteger i = 0; i<10; i++) {
        NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
    }
    if (self.isCancelled) return;
}

/**
 自定義操作的使用
 */
- (void)customOperation {
    CustomOperation *op = [CustomOperation new];
    [op start];
}

1.4.操作之間的關(guān)系 -- 操作依賴

NSOperation之間可以輕松的設(shè)置依賴來保證執(zhí)行順序

[operationB addDependency:operationA]; // 操作B依賴于操作A

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download1----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download2----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download3----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download4----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download5----%@", [NSThread  currentThread]);
    }];

    // 設(shè)置依賴
    [op3 addDependency:op1];
    [op3 addDependency:op2];
    [op3 addDependency:op4];
    
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue addOperation:op4];
    [queue addOperation:op5];

打印結(jié)果:操作3一定是在操作1,2,4執(zhí)行完后才執(zhí)行

2017-04-18 10:55:14.185 OperationDependency[2914:60630] download1----<NSThread: 0x61000007dc00>{number = 3, name = (null)}
2017-04-18 10:55:14.185 OperationDependency[2914:60633] download4----<NSThread: 0x60000007d840>{number = 5, name = (null)}
2017-04-18 10:55:14.185 OperationDependency[2914:60634] download5----<NSThread: 0x60000007c900>{number = 6, name = (null)}
2017-04-18 10:55:14.185 OperationDependency[2914:60632] download2----<NSThread: 0x618000267c40>{number = 4, name = (null)}
2017-04-18 10:55:14.189 OperationDependency[2914:60633] download3----<NSThread: 0x60000007d840>{number = 5, name = (null)}

demo

1.5操作的監(jiān)聽

NSOperation可以很容易的設(shè)置監(jiān)聽任務(wù)的完成,可以監(jiān)聽一個操作的執(zhí)行完畢

-(void (^)(void))completionBlock;

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

2、NSOperationQueue

NSOperation可以調(diào)用start方法來執(zhí)行任務(wù),但默認是同步執(zhí)行的
如果將NSOperation添加到NSOperationQueue(操作隊列)中,系統(tǒng)會自動異步執(zhí)行NSOperation中的操作
使用NSOperationQueue分兩步,隊列會根據(jù)系統(tǒng)情況,自行決定是否開啟線程

  • 創(chuàng)建操作
  • 將操作添加到隊列中

2.1 添加操作到NSOperationQueue中

-(void)addOperation:(NSOperation *)op;

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

  • 代碼
/**
    隊列添加操作
 */
- (void)queueAddOperation {
    // 1.創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 2.創(chuàng)建操作(操作把任務(wù)封裝起來)
    // 設(shè)置最大并發(fā)操作數(shù)
    //    queue.maxConcurrentOperationCount = 2;
    //queue.maxConcurrentOperationCount = 1; // 就變成了串行隊列
    
    // 2.1 創(chuàng)建NSInvocationOperation
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil];
    
    // 2.2 創(chuàng)建NSBlockOperation
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download3 --- %@", [NSThread currentThread]);
    }];
    
    [op3 addExecutionBlock:^{
        NSLog(@"download4 --- %@", [NSThread currentThread]);
    }];
    [op3 addExecutionBlock:^{
        NSLog(@"download5 --- %@", [NSThread currentThread]);
    }];
      
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download6 --- %@", [NSThread currentThread]);
    }];

    // 2.3 創(chuàng)建CustomOperation
    CustomOperation *op5 = [[CustomOperation alloc] init];
    
    // 3. 添加操作到隊列中
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue addOperation:op4];
    [queue addOperation:op5];
}

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

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

  • 打印結(jié)果
2017-04-17 23:43:00.974 NSOperation與NSOperationQueue初步使用[922:27537] download3 --- <NSThread: 0x60000026f040>{number = 6, name = (null)}
2017-04-17 23:43:00.974 NSOperation與NSOperationQueue初步使用[922:27518] download2 --- <NSThread: 0x608000273400>{number = 3, name = (null)}
2017-04-17 23:43:00.974 NSOperation與NSOperationQueue初步使用[922:27517] download1 --- <NSThread: 0x600000267180>{number = 4, name = (null)}
2017-04-17 23:43:00.974 NSOperation與NSOperationQueue初步使用[922:27520] download6 --- <NSThread: 0x60000026ea00>{number = 5, name = (null)}
2017-04-17 23:43:00.974 NSOperation與NSOperationQueue初步使用[922:27680] download4 --- <NSThread: 0x608000273040>{number = 7, name = (null)}
2017-04-17 23:43:00.974 NSOperation與NSOperationQueue初步使用[922:27537] download5 --- <NSThread: 0x60000026f040>{number = 6, name = (null)}

2.2 最大并發(fā)數(shù):同時執(zhí)行的任務(wù)數(shù)

隊列是串行還是并行,可以通過控制最大并發(fā)數(shù)決定

- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

    // 設(shè)置最大并發(fā)操作數(shù)
  queue.maxConcurrentOperationCount = 1; // 就變成了串行隊列

2.3 隊列的取消、暫停、恢復(fù)

和GCD一樣,都可以設(shè)置隊列的取消,暫停和恢復(fù)

- (void)cancelAllOperations;

- (void)setSuspended:(BOOL)b;

四、補充

1.GCD和NSOperation的區(qū)別

GCD的隊列類型

  • 并發(fā)隊列
    • 自己創(chuàng)建的
    • 全局
  • 串行隊列
    • 主隊列
    • 自己創(chuàng)建的

NSOperationQueue的隊列類型

  • 主隊列
    • [NSOperationQueue mainQueue]
    • 凡是添加到主隊列中的任務(wù)(NSOperation),都會放到主線程中執(zhí)行
  • 非主隊列(其他隊列)
    • [[NSOperationQueue alloc] init]
    • 同時包含了:串行、并發(fā)功能,可以通過設(shè)置最大并發(fā)數(shù)控制是串行還是并發(fā)隊列
    • 添加到這種隊列中的任務(wù)(NSOperation),就會自動放到子線程中執(zhí)行

主要區(qū)別:

  • GCD是純C語言的API,NSOperation是基于GCD的OC版本封裝
  • GCD只支持FIFO的隊列,NSOperation可以很方便地調(diào)整執(zhí)行順序,設(shè)置最大并發(fā)數(shù)量
  • NSOperationQueue可以輕松在operation間設(shè)置依賴關(guān)系,而GCD需要些很多代碼才能實現(xiàn)
  • NSOperationQueue支持KVO,可以檢測operation是否正在執(zhí)行(isExecuted),是否結(jié)束(isFinisn),是否取消(isCancel)
  • GCD的執(zhí)行速度比NSOperation快

2.線程間的通信

在iOS開發(fā)過程中,我們一般在主線程里邊進行UI刷新,例如:點擊、滾動、拖拽等事件。我們通常把一些耗時的操作放在其他線程,比如說圖片下載、文件上傳等耗時操作。而當(dāng)我們有時候在其他線程完成了耗時操作時,需要回到主線程,那么就用到了線程之間的通訊。

  • NSThread

    [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO]

  • GCD

/**
 線程之間的通信
 */
- (void)connectionBetweenThread {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 圖片的網(wǎng)絡(luò)路徑
        NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
        
        // 加載圖片
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        // 生成圖片
        UIImage *image = [UIImage imageWithData:data];
        
        // 回到主線程
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
        });
    });
}

  • NSOperationQueue
/**
 * 線程之間的通信
 */
- (void)connectionBetweenThread {
    [[[NSOperationQueue alloc] init] addOperationWithBlock:^{
        // 圖片的網(wǎng)絡(luò)路徑
       NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
        
        // 加載圖片
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        // 生成圖片
        UIImage *image = [UIImage imageWithData:data];
        
        // 回到主線程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.imageView.image = image;
        }];
    }];
}

線程安全

  • 1.多線程安全隱患引出

    假設(shè)火車站有3個賣票窗口,余票是1000,賣票窗口3個線程同一時刻讀取剩余票數(shù),都是讀取的1000,賣票線程1賣了一張 ,余票變成999。賣票線程2反應(yīng)慢點,在賣票線程1后面執(zhí)行賣票,因為賣票線程2剛開始讀取的余票也是1000,所以在賣掉一張后,余額也變成999。賣票線程3反應(yīng)更慢,在賣票線程2后面執(zhí)行賣票,因為賣票線程3剛開始讀取的余票也是1000,所以在賣掉一張后,余額依舊也變成999。所以出現(xiàn)了錯誤,本來賣了3張,可是余票還有999張。

    當(dāng)多個線程訪問同一塊資源時,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題

    比如多個線程訪問同一個對象、同一個變量、同一個文件,并對這一塊資源進行讀寫操作時,容易發(fā)生問題

無標(biāo)題.png
  • 2、多線程安全隱患代碼示例

    賣票demo

    // 創(chuàng)建線程

    // 創(chuàng)建3條線程
    - (void)viewDidLoad {
    [super viewDidLoad];
    self.ticketCount = 100;
    
    self.thread01 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread01.name = @"售票員01";
    
    self.thread02 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread02.name = @"售票員02";
    
    self.thread03 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread03.name = @"售票員03";
    

}

```

// 3條線程訪問資源
// 3條線程訪問資源
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self.thread01 start];
    [self.thread02 start];
    [self.thread03 start];
}

// 賣票方法

  - (void)saleTicket {
    while (1) {
        //@synchronized(self) {
            // 先取出總數(shù)
            NSInteger count = self.ticketCount;
            if (count > 0) {
                self.ticketCount = count - 1;
                NSLog(@"%@賣了一張票,還剩下%zd張", [NSThread currentThread].name, self.ticketCount);
            } else {
                NSLog(@"票已經(jīng)賣完了");
                break;
            }
       // }
    }
}
  • 3.多線程安全隱患解決方案

在線程A讀取數(shù)據(jù)后,加一把鎖,別的線程就不能訪問了,只允許加鎖的線程A訪問。當(dāng)這個加鎖線程操作完數(shù)據(jù)后,線程A解鎖,此時別的線程就能訪問了, 假設(shè)輪到線程B訪問,線程B也先加把鎖,保證只有自己能訪問,執(zhí)行完操作再解鎖....就這樣循環(huán)往復(fù),只要誰訪問就加把鎖,直到操作結(jié)束后再解鎖。這就是互斥鎖。

加鎖.png
注意:
1.盡管互斥鎖能夠有效防止因多線程搶奪資源而造成的數(shù)據(jù)安全問題,但是其需要消耗大量的CPU資源;
2.而且只有再多條線程搶奪同一塊資源的時候才使用互斥鎖
  • 4.熟悉SDWebImage的實現(xiàn)原理

以SDWebImage的實現(xiàn)原理為demo例子,熟悉如何自定義NSOperation, 如何使用NSOperationQueue以及線程之間如何通信,大家自己去理解(這個demo我對這位作者文章的學(xué)習(xí)模仿)
模仿SDWebImage的輕量圖片異步下載框架BBSDWebImage

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容