多線程GCD和NSOperation

目錄

一宾尚、基本概念

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

二、GCD

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

三牛郑、NSOperation和NSOperationQueue

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

四、補(bǔ)充

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

一淹朋、基本概念


1.多線程

  • 什么是多線程笙各?

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

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

    了解了多線程的原理后晨汹,你會(huì)有更深的理解

  • 多線程的原理

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

      • 如果要在1個(gè)線程中執(zhí)行多個(gè)任務(wù)豹储,那么只能一個(gè)一個(gè)地按順序執(zhí)行這些任,也就是說(shuō)淘这,在同一時(shí)間內(nèi)剥扣,1個(gè)線程只能執(zhí)行1個(gè)任務(wù)
    • 1個(gè)進(jìn)程中可以開(kāi)啟多條線程,每條線程可以并行(同時(shí))執(zhí)行不同的任務(wù)

      對(duì)于單核

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

      對(duì)于多核

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

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

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

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

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

并行指的是一種技術(shù)仙畦,一個(gè)同時(shí)處理多個(gè)任務(wù)的技術(shù)输涕。它描述了一種能夠同時(shí)處理多個(gè)任務(wù)的能力,側(cè)重點(diǎn)在于“運(yùn)行”慨畸。

并行的反義詞就是串行莱坎,表示任務(wù)必須按順序來(lái),一個(gè)一個(gè)執(zhí)行寸士,前一個(gè)執(zhí)行完了才能執(zhí)行后一個(gè)檐什。

并發(fā)指的是一種現(xiàn)象,一種經(jīng)常出現(xiàn)薪缆,無(wú)可避免的現(xiàn)象沮协。它描述的是“多個(gè)任務(wù)同時(shí)發(fā)生躏啰,需要被處理”這一現(xiàn)象。它的側(cè)重點(diǎn)在于“發(fā)生”瓮具。

  • 比如在一個(gè)景點(diǎn),一個(gè)游客買票對(duì)應(yīng)售票處來(lái)說(shuō)凡蜻,當(dāng)做一個(gè)任務(wù)搭综,而很多游客需要買票,同時(shí)發(fā)生划栓,需要被處理兑巾,這種現(xiàn)象稱為并發(fā)

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

  • 進(jìn)入景點(diǎn)的控制門蒋歌,一次只能一個(gè)人通過(guò),這叫串行委煤;

    我們經(jīng)常掛在嘴邊的“多線程”堂油,正是采用了并行技術(shù),從而提高了執(zhí)行效率碧绞。因?yàn)橛卸鄠€(gè)線程府框,所以計(jì)算機(jī)的多個(gè)CPU可以同時(shí)工作,同時(shí)處理不同線程內(nèi)的指令讥邻。

3.隊(duì)列與任務(wù)

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

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

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

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

不管是串行隊(duì)列(SerialQueue)還是并行隊(duì)列(ConcurrencyQueue),都是FIFO隊(duì)列踩寇。也就意味著啄清,任務(wù)一定是一個(gè)一個(gè)地,按照先進(jìn)先出的順序來(lái)調(diào)度俺孙。

4.同步與異步

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

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

所謂同步旨剥,就是在發(fā)出一個(gè)調(diào)用時(shí)咧欣,在沒(méi)有得到結(jié)果之前,該調(diào)用就不返回轨帜。但是一旦調(diào)用返回魄咕,就得到返回值了。

換句話說(shuō)蚌父,就是由調(diào)用者主動(dòng)等待這個(gè)調(diào)用的結(jié)果哮兰。

而異步則是相反,調(diào)用在發(fā)出之后苟弛,這個(gè)調(diào)用就直接返回了喝滞,所以沒(méi)有返回結(jié)果。換句話說(shuō)膏秫,當(dāng)一個(gè)異步過(guò)程調(diào)用發(fā)出后右遭,調(diào)用者不會(huì)立刻得到結(jié)果。而是在調(diào)用發(fā)出后,被調(diào)用者通過(guò)狀態(tài)狸演、通知來(lái)通知調(diào)用者言蛇,或通過(guò)回調(diào)函數(shù)處理這個(gè)調(diào)用。

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

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

5.線程狀態(tài)

一般來(lái)說(shuō)吨拗,線程有五個(gè)狀態(tài)

  • 新建狀態(tài):線程通過(guò)各種方式在被創(chuàng)建之初满哪,還沒(méi)有調(diào)用開(kāi)始執(zhí)行方法,這個(gè)時(shí)候的線程就是新建狀態(tài)劝篷;
    self.thread=[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
  • 就緒狀態(tài):在新建線程被創(chuàng)建之后調(diào)用了開(kāi)始執(zhí)行方法[thread start]哨鸭,但是CPU并不是真正的同時(shí)執(zhí)行多個(gè)任務(wù),所以要等待CPU調(diào)用娇妓,這個(gè)時(shí)候線程處于就緒狀態(tài)像鸡。處于就緒狀態(tài)的線程并不一定立即執(zhí)行線程里的代碼,線程還必須同其他線程競(jìng)爭(zhēng)CPU時(shí)間哈恰,只有獲得CPU時(shí)間才可以運(yùn)行線程只估。
  • 運(yùn)行狀態(tài):線程獲得CPU時(shí)間,被CPU調(diào)用之后就進(jìn)入運(yùn)行狀態(tài)
  • CPU 負(fù)責(zé)調(diào)度可調(diào)度線程池中線程的執(zhí)行
  • 線程執(zhí)行完成之前(死亡之前)着绷,狀態(tài)可能會(huì)在就緒和運(yùn)行之間來(lái)回切換
  • 就緒和運(yùn)行之間的狀態(tài)變化由 CPU 負(fù)責(zé)蛔钙,程序員不能干預(yù)
  • 阻塞狀態(tài):所謂阻塞狀態(tài)是正在運(yùn)行的線程沒(méi)有運(yùn)行結(jié)束,暫時(shí)讓出CPU荠医,這時(shí)其他處于就緒狀態(tài)的線程就可以獲得CPU時(shí)間吁脱,進(jìn)入運(yùn)行狀態(tài)。
  • 線程通過(guò)調(diào)用sleep方法進(jìn)入睡眠狀態(tài)[NSThread sleepForTimeInterval:2.0];
  • 線程調(diào)用一個(gè)在I/O上被阻塞的操作彬向,即該操作在輸入輸出操作完成之前不會(huì)返回到它的調(diào)用者
  • 線程試圖得到一個(gè)鎖兼贡,而該鎖正被其他線程持有;
  • 線程在等待某個(gè)觸發(fā)條件
  • 死亡狀態(tài):當(dāng)線程的任務(wù)結(jié)束娃胆,發(fā)生異常遍希,或者是強(qiáng)制退出這三種情況會(huì)導(dǎo)致線程的死亡。線程死亡后缕棵,線程對(duì)象從內(nèi)存中移除孵班。一旦線程進(jìn)入死亡狀態(tài),再次嘗試重新開(kāi)啟線程招驴,則程序會(huì)掛篙程。
@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.開(kāi)啟線程---->就緒和運(yùn)行狀態(tài)
  [self.thread start];
}

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

  //第二種設(shè)置線程阻塞2虱饿,以當(dāng)前時(shí)間為基準(zhǔn)阻塞4秒
  NSLog(@"接下來(lái),線程阻塞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.多線程方案

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

二、GCD


1. GCD簡(jiǎn)介

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

純C語(yǔ)言试伙,提供了非常多強(qiáng)大的函數(shù)

2.GCD的優(yōu)勢(shì)

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

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

GCD會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程于样、調(diào)度任務(wù)疏叨、銷毀線程)

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

3.任務(wù)和隊(duì)列

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

隊(duì)列:這里的隊(duì)列指任務(wù)隊(duì)列考廉,即用來(lái)存放任務(wù)的隊(duì)列。隊(duì)列是一種特殊的線性表携御,采用FIFO(先進(jìn)先出)的原則昌粤,即新任務(wù)總是被插入到隊(duì)列的末尾,而讀取任務(wù)的時(shí)候總是從隊(duì)列的頭部開(kāi)始讀取啄刹。每讀取一個(gè)任務(wù)涮坐,則從隊(duì)列中釋放一個(gè)任務(wù)

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

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

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

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

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

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

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

  • 串行隊(duì)列(Serial Dispatch Queue):任務(wù)按照順序被調(diào)度精盅,前一個(gè)任務(wù)不執(zhí)行完畢帽哑,隊(duì)列不會(huì)調(diào)度,這樣任務(wù)叹俏,都是一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后妻枕,再執(zhí)行下一個(gè)任務(wù))

    // 串行隊(duì)列的創(chuàng)建方法
    dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    
    // 使用主隊(duì)列(跟主線程相關(guān)聯(lián)的隊(duì)列)
    // 主隊(duì)列是GCD自帶的一種特殊的串行隊(duì)列,放在主隊(duì)列中的任務(wù),都會(huì)放到主線程中執(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ù)說(shuō)明:
queue:隊(duì)列
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ù):負(fù)責(zé)任務(wù)的執(zhí)行碉输,決定了任務(wù)的執(zhí)行方式籽前。
同步函數(shù)(sync):只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開(kāi)啟新線程的能力
異步執(zhí)行(async):可以在新的線程中執(zhí)行任務(wù)敷钾,具備開(kāi)啟新線程的能力

serial和concurrent:

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

各種組合的效果

并發(fā)隊(duì)列 創(chuàng)建串行隊(duì)列 主隊(duì)列
同步(sync) 1稠茂、沒(méi)有開(kāi)啟線程 2、串行執(zhí)行任務(wù) 1情妖、沒(méi)有開(kāi)啟線程 2睬关、串行執(zhí)行任務(wù) 1、沒(méi)有開(kāi)啟線程 2毡证、串行執(zhí)行任務(wù)
異步(async) 1电爹、有開(kāi)啟線程 2、并發(fā)執(zhí)行任務(wù) 1情竹、有開(kāi)啟線程 2藐不、串行執(zhí)行任務(wù) 1匀哄、沒(méi)有開(kāi)啟線程 2、串行執(zhí)行任務(wù)

代碼示范:

  • (1)用異步函數(shù)往并發(fā)隊(duì)列中添加任務(wù)
/**
 并發(fā)隊(duì)列+異步函數(shù)
 */
- (void)concurrentQueueInAsyn {
    //1.獲得全局的并發(fā)隊(duì)列
    dispatch_queue_t queue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //2.添加任務(wù)到隊(duì)列中雏蛮,就可以執(zhí)行任務(wù)
    //異步函數(shù):具備開(kāi)啟新線程的能力
    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é)果:同時(shí)開(kāi)啟三個(gè)子線程

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ù)往串行隊(duì)列中添加任務(wù)
/**
 串行隊(duì)列+異步函數(shù)
 */
- (void)serialQueueInAsyn {
    //打印主線程
    NSLog(@"主線程----%@",[NSThread mainThread]);
    
    //1.創(chuàng)建串行隊(duì)列
    dispatch_queue_t  queue= dispatch_queue_create("11", NULL);
    //第一個(gè)參數(shù)為串行隊(duì)列的名稱涎嚼,是c語(yǔ)言的字符串
    //第二個(gè)參數(shù)為隊(duì)列的屬性,一般來(lái)說(shuō)串行隊(duì)列不需要賦值任何屬性挑秉,所以通常傳空值(NULL)
    
    //2.添加任務(wù)到隊(duì)列中執(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é)果:會(huì)開(kāi)啟線程法梯,但是只開(kāi)啟一個(gè)線程

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ā)隊(duì)列中添加任務(wù)
/**
 異步隊(duì)列+同步函數(shù)
 */
- (void)concurrentQueueInSyn {
    //打印主線程
    NSLog(@"主線程----%@",[NSThread mainThread]);
    
    //1.創(chuàng)建并行隊(duì)列
    dispatch_queue_t  queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    
    //2.添加任務(wù)到隊(duì)列中執(zhí)行
    dispatch_sync(queue, ^{
        NSLog(@"下載圖片1----%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"下載圖片2----%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"下載圖片3----%@",[NSThread currentThread]);
    });
}

打印結(jié)果:不會(huì)開(kāi)啟新的線程,并發(fā)隊(duì)列失去了并發(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ù)往串行隊(duì)列中添加任務(wù)
/**
 串行隊(duì)列+同步函數(shù)
 */
- (void)serialQueueInSyn {
    NSLog(@"用同步函數(shù)往串行隊(duì)列中添加任務(wù)");
    //打印主線程
    NSLog(@"主線程----%@",[NSThread mainThread]);
    
    //創(chuàng)建串行隊(duì)列
    dispatch_queue_t  queue= dispatch_queue_create("11", NULL);
    
    //2.添加任務(wù)到隊(duì)列中執(zhí)行
    dispatch_sync(queue, ^{
        NSLog(@"下載圖片1----%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"下載圖片2----%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"下載圖片3----%@",[NSThread currentThread]);
    });
}

打印結(jié)果:不會(huì)開(kāi)啟新的線程

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í)行處理,因?yàn)镸ain Dispatch Queue 在主線程的Runloop中執(zhí)行姻灶,所以在比如每隔1/60秒執(zhí)行的Runloop中铛绰,Block最快在3s后執(zhí)行,最慢在3s+1/60s后執(zhí)行产喉,并且在Main Dispatch Queue有大量處理追加或者主線程的處理本身有延遲時(shí)捂掰,這個(gè)時(shí)間會(huì)更長(zhǎng),所以有嚴(yán)格時(shí)間的要求下使用時(shí)會(huì)出現(xiàn)問(wè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ù)能保證某段代碼在程序運(yùn)行過(guò)程中只被執(zhí)行1次

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

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

});

整個(gè)程序運(yùn)行過(guò)程中曾沈,只會(huì)執(zhí)行一次这嚣。

7.3 隊(duì)列組

有這么1種需求:

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

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

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

dispatch_group_t group =  dispatch_group_create();

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

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

});

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

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

});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

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

});

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

  • 1.沒(méi)有使用group的方法
/**
 沒(méi)有使用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]);
        });
    });
}

//封裝一個(gè)方法罐旗,傳入一個(gè)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)建一個(gè)隊(duì)列組
    dispatch_group_t group = dispatch_group_create();
    
    //2.開(kāi)啟一個(gè)任務(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.開(kāi)啟一個(gè)任務(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]);
    });
    
    //同時(shí)執(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;
        
        //合并兩張圖片
        //注意最后一個(gè)參數(shù)是浮點(diǎn)數(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é)果:同時(shí)開(kāi)啟了兩個(gè)子線程颤霎,分別下載圖片

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)建多條線程,進(jìn)行迭代涂滴,速度快

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

在訪問(wèn)數(shù)據(jù)庫(kù)或文件時(shí)友酱,使用Serial Dispatch Queue 可避免數(shù)據(jù)競(jìng)爭(zhēng)的問(wèn)題。

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

也就是說(shuō)搁料,為了高效率的進(jìn)行訪問(wèn)或详,讀取處理追加到Concurrent Dispatch Queue中系羞,寫入處理在任何一個(gè)讀取處理沒(méi)有執(zhí)行的狀態(tài)下,追加到Serial Dispatch Queue中即可(在寫入處理之前霸琴,讀取處理不可執(zhí)行)

 /**
 并發(fā)隊(duì)列+異步函數(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ù)會(huì)等待追加到Concurrent Dispatch Queue上的并行執(zhí)行的處理全部結(jié)束之后澎迎,再將指定的處理追加到該Concurrent Dispatch Queue中,然后在由dispatch_barrier_async函數(shù)追加的處理執(zhí)行完畢后选调,Concurrent Dispatch Queue才恢復(fù)為一般的動(dòng)作夹供,追加到該Concurrent Dispatch Queue的處理又開(kāi)始并行執(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");
    });
    // 將這個(gè)block之前的任務(wù)攔著,等執(zhí)行完后仁堪,執(zhí)行這個(gè)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ù)可以實(shí)現(xiàn)高效率的數(shù)據(jù)庫(kù)訪問(wèn)和文件訪問(wèn)弦聂。
demo地址

7.6.補(bǔ)充

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

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

    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ù)會(huì)立即阻塞調(diào)用時(shí)該函數(shù)所在的線程,即主線程等待dispatch_sync()函數(shù)返回

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

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

也就是說(shuō)徙融,主線程等待dispatch_sync函數(shù)返回,而dispatch_sync函數(shù)將block添加到主線程瑰谜,主線程因?yàn)橐萪ispatch_sync函數(shù)返回才去執(zhí)行block欺冀,主線程被阻塞,沒(méi)有機(jī)會(huì)去執(zhí)行block,發(fā)生死鎖萨脑。

解決上述問(wèn)題的死鎖隐轩,也簡(jiǎn)單,使用Global Dispatch Queue進(jìn)行處理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ù)會(huì)立即阻塞調(diào)用時(shí)該函數(shù)所在的線程,全局并發(fā)線程等待dispatch_sync()函數(shù)返回

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

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

三悴灵、NSOperation和NSOperationQueue

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

1.NSOperation

NSOperation是個(gè)抽象類骂蓖,并不具備封裝操作的能力积瞒,必須使用它的子類:

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

1.1NSInvocationOperation

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

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

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

- (void)start;

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

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

- (void)run {
    NSLog(@"------%@", [NSThread currentThread]);
}
  • 打印結(jié)果:不開(kāi)啟線程缰贝,在當(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對(duì)象

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

b.通過(guò)addExecutionBlock:方法添加更多的操作

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

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

- (void)start;

注意:只要NSBlockOperation封裝的操作數(shù) > 1馍悟,就會(huì)異步執(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.啟動(dòng)操作
    [op start];
}
  • 打印結(jié)果:第一個(gè)任務(wù)在當(dāng)前當(dāng)前線程,后面添加的任務(wù)會(huì)放在新的線程里執(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方法揩瞪,在里面實(shí)現(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è)置依賴來(lái)保證執(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)聽(tīng)

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

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

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

2嗤攻、NSOperationQueue

NSOperation可以調(diào)用start方法來(lái)執(zhí)行任務(wù)毛嫉,但默認(rèn)是同步執(zhí)行的
如果將NSOperation添加到NSOperationQueue(操作隊(duì)列)中,系統(tǒng)會(huì)自動(dòng)異步執(zhí)行NSOperation中的操作
使用NSOperationQueue分兩步妇菱,隊(duì)列會(huì)根據(jù)系統(tǒng)情況承粤,自行決定是否開(kāi)啟線程

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

2.1 添加操作到NSOperationQueue中

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

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

  • 代碼
/**
    隊(duì)列添加操作
 */
- (void)queueAddOperation {
    // 1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 2.創(chuàng)建操作(操作把任務(wù)封裝起來(lái))
    // 設(shè)置最大并發(fā)操作數(shù)
    //    queue.maxConcurrentOperationCount = 2;
    //queue.maxConcurrentOperationCount = 1; // 就變成了串行隊(duì)列
    
    // 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. 添加操作到隊(duì)列中
    [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ù):同時(shí)執(zhí)行的任務(wù)數(shù)

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

- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

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

2.3 隊(duì)列的取消闯团、暫停辛臊、恢復(fù)

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

- (void)cancelAllOperations;

- (void)setSuspended:(BOOL)b;

四彻舰、補(bǔ)充

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

GCD的隊(duì)列類型

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

NSOperationQueue的隊(duì)列類型

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

主要區(qū)別:

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

2.線程間的通信

在iOS開(kāi)發(fā)過(guò)程中,我們一般在主線程里邊進(jìn)行UI刷新崖堤,例如:點(diǎn)擊侍咱、滾動(dòng)、拖拽等事件密幔。我們通常把一些耗時(shí)的操作放在其他線程楔脯,比如說(shuō)圖片下載、文件上傳等耗時(shí)操作胯甩。而當(dāng)我們有時(shí)候在其他線程完成了耗時(shí)操作時(shí)昧廷,需要回到主線程堪嫂,那么就用到了線程之間的通訊。

  • 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個(gè)賣票窗口木柬,余票是1000皆串,賣票窗口3個(gè)線程同一時(shí)刻讀取剩余票數(shù),都是讀取的1000,賣票線程1賣了一張 ,余票變成999眉枕。賣票線程2反應(yīng)慢點(diǎn)恶复,在賣票線程1后面執(zhí)行賣票,因?yàn)橘u票線程2剛開(kāi)始讀取的余票也是1000,所以在賣掉一張后,余額也變成999速挑。賣票線程3反應(yīng)更慢谤牡,在賣票線程2后面執(zhí)行賣票,因?yàn)橘u票線程3剛開(kāi)始讀取的余票也是1000,所以在賣掉一張后,余額依舊也變成999姥宝。所以出現(xiàn)了錯(cuò)誤,本來(lái)賣了3張,可是余票還有999張翅萤。

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

    比如多個(gè)線程訪問(wèn)同一個(gè)對(duì)象腊满、同一個(gè)變量套么、同一個(gè)文件,并對(duì)這一塊資源進(jìn)行讀寫操作時(shí)碳蛋,容易發(fā)生問(wèn)題

無(wú)標(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條線程訪問(wèn)資源
// 3條線程訪問(wèn)資源
-(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ù)后肃弟,加一把鎖诸迟,別的線程就不能訪問(wèn)了,只允許加鎖的線程A訪問(wèn)愕乎。當(dāng)這個(gè)加鎖線程操作完數(shù)據(jù)后,線程A解鎖壁公,此時(shí)別的線程就能訪問(wèn)了, 假設(shè)輪到線程B訪問(wèn)感论,線程B也先加把鎖,保證只有自己能訪問(wèn)紊册,執(zhí)行完操作再解鎖....就這樣循環(huán)往復(fù)比肄,只要誰(shuí)訪問(wèn)就加把鎖,直到操作結(jié)束后再解鎖囊陡。這就是互斥鎖芳绩。

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嘹害,一起剝皮案震驚了整個(gè)濱河市撮竿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌笔呀,老刑警劉巖幢踏,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異许师,居然都是意外死亡房蝉,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門微渠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)搭幻,“玉大人,你說(shuō)我怎么就攤上這事敛助〈植罚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵纳击,是天一觀的道長(zhǎng)续扔。 經(jīng)常有香客問(wèn)我,道長(zhǎng)焕数,這世上最難降的妖魔是什么纱昧? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮堡赔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘善已。我一直安慰自己,他們只是感情好换团,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布悉稠。 她就那樣靜靜地躺著,像睡著了一般瑰抵。 火紅的嫁衣襯著肌膚如雪渣磷。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天始花,我揣著相機(jī)與錄音,去河邊找鬼甚纲。 笑死,一個(gè)胖子當(dāng)著我的面吹牛朦前,可吹牛的內(nèi)容都是我干的介杆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼韭寸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼春哨!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起恩伺,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤赴背,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后晶渠,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體凰荚,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年乱陡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了浇揩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡憨颠,死狀恐怖胳徽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情爽彤,我是刑警寧澤养盗,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站适篙,受9級(jí)特大地震影響往核,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嚷节,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一聂儒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧硫痰,春花似錦衩婚、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春奇昙,著一層夾襖步出監(jiān)牢的瞬間护侮,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工储耐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留羊初,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓弧岳,卻偏偏與公主長(zhǎng)得像凳忙,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子禽炬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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