iOS多線程: GCD

系列文章:

多線程

多線程 pthread、NSThread

多線程 GCD

多線程 NSOperation

多線程運用

GCD

全稱是Grand Central Dispatch,“偉大的中樞調(diào)度器”。GCD是蘋果為多核的并行運算提出的解決方案,所以會自動合理地利用更多的CPU內(nèi)核(比如雙核、四核),最重要的是它會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程),程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼。同時它使用的也是 c語言,提供了非常多強大的函數(shù), 由于使用了 Block(Swift里叫做閉包),使得使用起來更加方便,而且靈活。所以基本上大家都使用 GCD 這套方案。

任務(wù)和隊列

GCD中有2個核心概念

任務(wù):執(zhí)行什么操作

  • 即操作,你想要干什么,說白了就是一段代碼,在 GCD 中就是一個 Block,所以添加任務(wù)十分方便。任務(wù)有兩種執(zhí)行方式: 同步執(zhí)行異步執(zhí)行,他們之間的區(qū)別是 能不能開啟新的線程。

    同步:只是在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力

    異步:可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力

  • 同步在當(dāng)前線程中執(zhí)行,任務(wù)會立即執(zhí)行,它會阻塞當(dāng)前線程并等待 Block 中的任務(wù)執(zhí)行完畢,然后當(dāng)前線程才會繼續(xù)運行,就是在發(fā)出一個功能調(diào)用時,在沒有得到結(jié)果之前,該調(diào)用就不繼續(xù)往下運行(調(diào)用)。是一定不會開新線程的,也就是必須一件一件事做,等前一件做完了才能做下一件事。(一個線程,當(dāng)前線程。);

  • 異步:可以開新線程,可以在新線程內(nèi)執(zhí)行任務(wù),并不意味著一定就會開新線程;任務(wù)不會立即執(zhí)行,當(dāng)前線程會直接往下執(zhí)行,它不會阻塞當(dāng)前線程。當(dāng)一個異步過程調(diào)用發(fā)出后,調(diào)用者不能立刻得到結(jié)果。實際處理這個調(diào)用的任務(wù)在完成后,通過狀態(tài)、通知和回調(diào)來通知調(diào)用者。(多個線程,開辟出來的新線程。);

  • 同步(sync) 和 異步(async) 的主要區(qū)別在于會不會阻塞當(dāng)前線程,直到 Block 中的任務(wù)執(zhí)行完畢。

隊列:用來存放任務(wù)

  • 隊列:用來存放任務(wù)

  • 一共有兩種隊列, 串行隊列并行隊列

    放到串行隊列的任務(wù),GCD 會 FIFO(先進先出) 地取出來一個,執(zhí)行一個,然后取下一個,這樣一個一個的執(zhí)行。

    放到并行隊列的任務(wù),根據(jù)同步或異步有不同的執(zhí)行方式. GCD 也會 FIFO的取出來,但不同的是,它取出來一個就會放到別的線程,然后再取出來一個又放到另一個的線程。這樣由于取的動作很快,忽略不計,看起來,所有的任務(wù)都是一起執(zhí)行的。不過需要注意,GCD 會根據(jù)系統(tǒng)資源控制并行的數(shù)量,所以如果任務(wù)很多,它并不會讓所有任務(wù)同時執(zhí)行。有高、默認、低和后臺4個優(yōu)先級。并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效.

1、并發(fā)隊列(Concurrent Dispatch Queue)
可以讓多個任務(wù)并發(fā)(同時)執(zhí)行(自動開啟多個線程同時執(zhí)行任務(wù))
并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效

2、串行隊列(Serial Dispatch Queue)
讓任務(wù)一個接著一個地執(zhí)行(一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))

同步執(zhí)行 異步執(zhí)行
串行隊列 當(dāng)前線程,一個一個執(zhí)行 其他線程,一個一個執(zhí)行
并行隊列 當(dāng)前線程,一個一個執(zhí)行 開很多線程,一起執(zhí)行
并行隊列 手動創(chuàng)建串行隊列 主隊列
同步執(zhí)行 當(dāng)前線程,一個一個執(zhí)行 當(dāng)前線程,一個一個執(zhí)行 沒有開啟新線程, 一個一個執(zhí)行
異步執(zhí)行 開很多線程,一起執(zhí)行 其他線程,一個一個執(zhí)行 沒有開啟新線程, 一個一個執(zhí)行
image.png

GCD使用步驟

  1. 定制任務(wù):確定想做的事情
  2. 將任務(wù)添加到隊列中:GCD會自動將隊列中的任務(wù)取出,放到對應(yīng)的線程中執(zhí)行。Tips:任務(wù)的取出遵循隊列的FIFO原則:先進先出,后進后出

創(chuàng)建任務(wù)

queue:隊列

block:任務(wù)

  • 同步任務(wù): 會阻塞當(dāng)前線程 (SYNC),不具備開啟新線程的能力
    dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// OBJECTIVE-C
  dispatch_sync(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });

// SWIFT
  dispatch_sync(<#queue#>, { () -> Void in
      //code here
      println(NSThread.currentThread())
  })
  • 異步任務(wù):不會阻塞當(dāng)前線程 (ASYNC),具備開啟新線程的能力
    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
OBJECTIVE-C
  dispatch_async(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });

SWIFT
  dispatch_async(<#queue#>, { () -> Void in
      //code here
      println(NSThread.currentThread())
  })

GCD中還有個用來執(zhí)行任務(wù)的函數(shù)(柵欄-dispatch_barrier) 在前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,而且它后面的任務(wù)等它執(zhí)行完成之后才會執(zhí)行, 后面會講到

創(chuàng)建隊列

  • 1、使用dispatch_get_main_queue()獲取主隊列

    主隊列:(跟主線程相關(guān)聯(lián)的隊列)是GCD自帶的一種特殊的 串行隊列。放在主隊列中的任務(wù),都會放到主線程中執(zhí)行。任何需要刷新 UI 的工作都要在主隊列執(zhí)行,所以一般耗時的任務(wù)都要放到別的線程執(zhí)行

      //OBJECTIVE-C
  dispatch_queue_t queue = dispatch_get_main_queue();

  //SWIFT
  let queue = dispatch_get_main_queue()

特點:
a、主隊列是與主線程相關(guān)聯(lián)的隊列
b、主隊列是GCD自帶的一種特殊的串行隊列
c、放在主隊列中的任務(wù),都會放到主線程中執(zhí)行

  • 2、使用dispatch_queue_create函數(shù)創(chuàng)建隊列 (串行隊列, 并行隊列)

dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)

label:隊列名稱

attr:隊列屬性

DISPATCH_QUEUE_SERIAL 或NULL:串行隊列

DISPATCH_QUEUE_CONCURRENT:并發(fā)隊列

//OBJECTIVE-C
  //串行隊列
  dispatch_queue_t queue = dispatch_queue_create("serial_queue", NULL);
  dispatch_queue_t queue = dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL);
  //并行隊列
  dispatch_queue_t queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);

  //SWIFT
  //串行隊列
  let queue = dispatch_queue_create("tk.bourne.testQueue", nil);
  let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL)
  //并行隊列
  let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT)
  • 3、使用dispatch_get_global_queue獲取全局并發(fā)隊列

dispatch_get_global_queue(long identifier, unsigned long flags);
identifier: 隊列的優(yōu)先級
flags:此參數(shù)暫時無用,用0即可
全局并發(fā)隊列的優(yōu)先級

#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺
  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

  //SWIFT
  let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

特點:GCD默認已經(jīng)提供了全局的并發(fā)隊列,供整個應(yīng)用使用,不需要手動創(chuàng)建

內(nèi)存管理補充:
如果你部署的最低目標低于 iOS 6.0 or Mac OS X 10.8
你應(yīng)該自己管理GCD對象,使用(dispatch_retain,dispatch_release),ARC并不會去管理它們
如果你部署的最低目標是 iOS 6.0 or Mac OS X 10.8 或者更高的
ARC已經(jīng)能夠管理GCD對象了,這時候,GCD對象就如同普通的OC對象一樣,不應(yīng)該使用dispatch_retain or dispatch_release

柵欄-dispatch_barrier

    Swift
    func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t):
    OC: 
    dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

這個方法重點是你傳入的 queue,當(dāng)你傳入的 queue 是通過 DISPATCH_QUEUE_CONCURRENT 參數(shù)自己創(chuàng)建的 queue 時,這個方法會阻塞這個 queue(注意是阻塞 queue ,而不是阻塞當(dāng)前線程),一直等到這個 queue 中排在它前面的任務(wù)都執(zhí)行完成后才會開始執(zhí)行自己,自己執(zhí)行完畢后,再會取消阻塞,使這個 queue 中排在它后面的任務(wù)繼續(xù)執(zhí)行。

如果你傳入的是其他的 queue, 那么它就和 dispatch_async 一樣了。

func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

這個方法的使用和上一個一樣,傳入 自定義的并發(fā)隊列 DISPATCH_QUEUE_CONCURRENT,它和上一個方法一樣的阻塞 queue,不同的是 這個方法還會 阻塞當(dāng)前線程。
如果你傳入的是其他的 queue, 那么它就和 dispatch_sync 一樣了。

特別注意:使用sync函數(shù)往當(dāng)前串行隊列中添加任務(wù),會卡住當(dāng)前的串行隊列 (線程卡死)

4個術(shù)語比較容易混淆:同步、異步、并發(fā)、串行

1.同步和異步主要影響:能不能開啟新的線程

同步:只是在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力

異步:可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力

2.并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式

并發(fā):多個任務(wù)并發(fā)(同時)執(zhí)行

串行:一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù)

GCD運用

一、線程間通信

在1個進程中,線程往往不是孤立存在的,多個線程之間需要經(jīng)常進行通信
例如在子線程下載圖片,在主線程刷新UI顯示圖片。

1、調(diào)用NSObject的方法

  • 在主線程上執(zhí)行操作
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

  • 在指定線程上執(zhí)行操作
    - (void)performSelector:(SEL)aSelector onThread:(NSThread*)thr withObject:(id)arg waitUnti

2、GCD方法:

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

二、延時執(zhí)行

iOS常見的延時執(zhí)行有2種方式

1、調(diào)用NSObject的方法

[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒后調(diào)用self的run方法

2、使用GCD函數(shù)

延遲執(zhí)行, 這段代碼將會在2秒后將任務(wù)插入RunLoop當(dāng)中
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2.0* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后異步執(zhí)行這里的代碼...
});

三、一次性代碼

// 使用dispatch_once函數(shù)能保證某段代碼在程序運行過程中只被執(zhí)行1次
單例的時候會使用到,避免返回的對象不是同一個對象
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行1次的代碼(這里面默認是線程安全的):不會有其他線程可以訪問到這里
});

單例模式

作用:
可以保證在程序運行過程,一個類只有一個實例,而且該實例易于供外界訪問。從而方便地控制了實例個數(shù),并節(jié)約系統(tǒng)資源

使用場合:
在整個應(yīng)用程序中,共享一份資源(這份資源只需要創(chuàng)建初始化1次)

static HLUserManager *_singleton = nil;
@implementation HLUserManager

+ (instancetype)sharedManager {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _singleton = [[HLUserManager alloc] init];
    });
    return _singleton;
}

四、快速迭代

dispatch_apply類似一個for循環(huán),并發(fā)的執(zhí)行每一項。所有任務(wù)結(jié)束后,dispatch_apply才會返回,會阻塞當(dāng)前線程。如果傳入隊列是串行隊列,要注意防止死鎖現(xiàn)象的發(fā)生。
循環(huán)執(zhí)行任務(wù),任務(wù)的順序是無序列的并且會堵塞當(dāng)前的線程。

    // 使用dispatch_apply函數(shù)能進行快速迭代遍歷
    // 第一個參數(shù): 迭代次數(shù),第二個參數(shù): 線程隊列(并發(fā)隊列) ,第三個參數(shù): index 索引 block: 任務(wù)
    dispatch_apply(10, dispatch_get_global_queue(0,0), ^(size_t index){
        // 執(zhí)行10次代碼,index順序不確定
               NSLog(@"GCD- %zd -- %@", index, [NSThread currentThread]); 
    });

五、隊列組

   分別異步執(zhí)行2個耗時的操作、2個異步操作都執(zhí)行完畢后,再回到主線程執(zhí)行操作
    // 1.實例化一個調(diào)度組
    dispatch_group_t group = dispatch_group_create();
    // 2.獲取一個全局隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 3.把任務(wù)添加進隊列中

    dispatch_group_async(group, queue, ^{
        // 執(zhí)行1個耗時的異步操作
        
    });
    dispatch_group_async(group, queue, ^{
        // 執(zhí)行1個耗時的異步操作
        
    });
       // 4.獲得所有調(diào)度任務(wù)都完成后的通知 
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的異步操作都執(zhí)行完畢后,回到主線程...
               NSLog(@"更新UI");
 
    });

手動管理group關(guān)聯(lián)的block的運行狀態(tài)(或計數(shù)),進入和退出group次數(shù)必須匹配

dispatch_group_enter(group);

dispatch_group_leave(group);

隊列組可以將很多隊列添加到一個組里,這樣做的好處是,當(dāng)這個組里所有的任務(wù)都執(zhí)行完了,隊列組會通過一個方法通知我們。下面是使用方法,這是一個很實用的功能。

SWIFT
//1.創(chuàng)建隊列組
let group = dispatch_group_create()
//2.創(chuàng)建隊列
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

//3.多次使用隊列組的方法執(zhí)行任務(wù), 只有異步方法
//3.1.執(zhí)行3次循環(huán)
dispatch_group_async(group, queue) { () -> Void in
    for _ in 0..<3 {
        NSLog("group-01 - %@", NSThread.currentThread())
    }
}

//3.2.主隊列執(zhí)行8次循環(huán)
dispatch_group_async(group, dispatch_get_main_queue()) { () -> Void in
    for _ in 0..<8 {
        NSLog("group-02 - %@", NSThread.currentThread())
    }
}

//3.3.執(zhí)行5次循環(huán)
dispatch_group_async(group, queue) { () -> Void in
    for _ in 0..<5 {
        NSLog("group-03 - %@", NSThread.currentThread())
    }
}

//4.都完成后會自動通知
dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in
    NSLog("完成 - %@", NSThread.currentThread())
}

打印結(jié)果

2015-07-28 03:40:34.277 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}
2015-07-28 03:40:34.277 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.277 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.277 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.278 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.277 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number = 2, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.278 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number = 2, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.278 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number = 2, name = (null)}
2015-07-28 03:40:34.279 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.279 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}
2015-07-28 03:40:34.279 test[12540:3319146] 完成 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

六、GCD 計時器應(yīng)用

NSTimer 的定時器是在 RunLoop 中實現(xiàn)的,由于RunLoop在處理各種任務(wù),所以會造成計時器不夠準確,有時候會相對慢一些,有沒有什么方法會讓計時變得準確?有,使用 GCD 的計時器方法會讓計時器變得相對準確,而且GCD不受RunLoop的 Mode 影響。

我們需要做的是,選擇其隊列類型,這里我選擇的是全局隊列。

dispatch Queue :決定了將來回調(diào)的方法在哪里執(zhí)行。

dispatch_source_set_timer 方法:
// dispatch_source_t timer 第一個參數(shù): 是一個OC對象

// DISPATCH_TIME_NOW 第二個參數(shù):定時器開始時間,也可以使用如下的方法,在Now 的時間基礎(chǔ)上再延時多長時間執(zhí)行以下任務(wù)。

 dispatch_time(<#dispatch_time_t when#>, <#int64_t delta#>)
 
 dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 0 * NSEC_PER_SEC);
//    dispatch_time_t start = dispatch_walltime(NULL, 0);

// intervalInSeconds 第三個參數(shù):定時器開始后的間隔時間(納秒 NSEC_PER_SEC)

// leewayInSeconds 第四個參數(shù):間隔精準度,0代標最精準,傳入一個大于0的數(shù),代表多少秒的范圍是可以接收的,主要為了提高程序性能,積攢一定的時間,Runloop執(zhí)行完任務(wù)會睡覺,這個方法讓他多睡一會,積攢時間,任務(wù)也就相應(yīng)多了一點,而后一起執(zhí)行

 // 全局隊列
dispatch_queue_t  queue = dispatch_get_global_queue(0, 0);
// 創(chuàng)建一個 timer 類型定時器 ( DISPATCH_SOURCE_TYPE_TIMER)
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//設(shè)置定時器的各種屬性(何時開始,間隔多久執(zhí)行)
// GCD 的時間參數(shù)一般為納秒 (1 秒 = 10 的 9 次方 納秒)
// 指定定時器開始的時間和間隔的時間
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC, 0);
// 任務(wù)回調(diào)
dispatch_source_set_event_handler(timer, ^{
    NSLog(@"-----定時器-------");
});
// 開始定時器任務(wù)(定時器默認開始是暫停的,需要復(fù)位開啟)
dispatch_resume(timer);

開始定時器

dispatch_resume(self.timer);

暫停定時器

dispatch_suspend(self.timer);

取消定時器

dispatch_cancel(self.timer);
self.timer = nil;

GCD實現(xiàn)驗證碼倒計時按鈕案例:

// 開啟倒計時效果
(IBAction)openCountdown:(id)sender {
    __block NSInteger time = 59; //倒計時時間
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
 dispatch_source_set_timer(timer,DISPATCH_TIME_NOW,1.0*NSEC_PER_SEC, 0); //每秒執(zhí)行
    dispatch_source_set_event_handler(timer, ^{
        if(time <= 0){ //倒計時結(jié)束,關(guān)閉
            dispatch_source_cancel(timer);
            dispatch_async(dispatch_get_main_queue(), ^{
                //設(shè)置按鈕的樣式
                [self.openSeconds setTitle:@"重新發(fā)送" forState:UIControlStateNormal];
                self.timeLabel.text = @"開始";
                self.openSeconds.userInteractionEnabled = YES;
            });
        }else{
            int seconds = time % 60;
            dispatch_async(dispatch_get_main_queue(), ^{
                //設(shè)置label讀秒效果
                self.timeLabel.text = [NSString stringWithFormat:@"重新發(fā)送(%.2d)",seconds];
                [self.openSeconds setTitle:@"已發(fā)送" forState:UIControlStateNormal];
              // 在這個狀態(tài)下 用戶交互關(guān)閉,防止再次點擊 button 再次計時
                self.openSeconds.userInteractionEnabled = NO;
            });
            time--;
        }
    });
    dispatch_resume(timer);
}

七、柵欄-dispatch_barrier

需求點:雖然我們有時要執(zhí)行幾個不同的異步任務(wù),但是我們還是要將其分成兩組:當(dāng)?shù)谝唤M異步任務(wù)都執(zhí)行完成后才執(zhí)行第二組的異步任務(wù)。這里的組可以包含一個任務(wù),也可以包含多個任務(wù)。
為了實現(xiàn)這個需求,我們需要使用dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
在兩組任務(wù)之間形成“柵欄”,使其“下方”的異步任務(wù)在其“上方”的異步任務(wù)都完成之前是無法執(zhí)行的。

    dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"----任務(wù) 1-----"); });
    dispatch_async(queue, ^{
        NSLog(@"----任務(wù) 2-----"); });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"----任務(wù) 3-----");
    });
    dispatch_async(queue, ^{
        NSLog(@"----任務(wù) 4-----"); });

為了更好的理解同步和異步,和各種隊列的使用,下面看兩個示例:

面試題: 以下代碼在主線程調(diào)用,結(jié)果是什么?

NSLog("之前 - %@", NSThread.currentThread())
dispatch_sync(dispatch_get_main_queue(), { () -> Void in 
        NSLog("sync - %@", NSThread.currentThread())
})
NSLog("之后 - %@", NSThread.currentThread())

答案

只會打印第一句:之前 - <NSThread: 0x7fb3a9e16470>{number = 1, name = main} ,然后主線程就卡死了,你可以在界面上放一個按鈕,你就會發(fā)現(xiàn)點不了了。

解釋

同步任務(wù)會阻塞當(dāng)前線程,然后把 Block 中的任務(wù)放到指定的隊列中執(zhí)行,只有等到 Block 中的任務(wù)完成后才會讓當(dāng)前線程繼續(xù)往下運行。

那么這里的步驟就是:打印完第一句后,dispatch_sync 立即阻塞當(dāng)前的主線程,然后把 Block 中的任務(wù)放到 main_queue 中,可是 main_queue 中的任務(wù)會被取出來放到主線程中執(zhí)行,但主線程這個時候已經(jīng)被阻塞了,所以 Block 中的任務(wù)就不能完成,它不完成,dispatch_sync 就會一直阻塞主線程,這就是死鎖現(xiàn)象。導(dǎo)致主線程一直卡死。

面試題: 以下代碼會產(chǎn)生什么結(jié)果?

let queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL)

NSLog("之前 - %@", NSThread.currentThread())

dispatch_async(queue, { () -> Void in
    NSLog("sync之前 - %@", NSThread.currentThread())
    dispatch_sync(queue, { () -> Void in
         NSLog("sync - %@", NSThread.currentThread())
    })
    NSLog("sync之后 - %@", NSThread.currentThread())
})

NSLog("之后 - %@", NSThread.currentThread())

答案:

2015-07-30 02:06:51.058 test[33329:8793087] 之前 - <NSThread: 0x7fe32050dbb0>{number = 1, name = main}
2015-07-30 02:06:51.059 test[33329:8793356] sync之前 - <NSThread: 0x7fe32062e9f0>{number = 2, name = (null)}
2015-07-30 02:06:51.059 test[33329:8793087] 之后 - <NSThread: 0x7fe32050dbb0>{number = 1, name = main}

很明顯 sync - %@sync之后 - %@ 沒有被打印出來!這是為什么呢?我們再來分析一下:

分析:

我們按執(zhí)行順序一步步來哦:

  1. 使用 DISPATCH_QUEUE_SERIAL 這個參數(shù),創(chuàng)建了一個 串行隊列。
  2. 打印出 之前 - %@ 這句。
  3. dispatch_async 異步執(zhí)行,所以當(dāng)前線程不會被阻塞,于是有了兩條線程,一條當(dāng)前線程繼續(xù)往下打印出 之后 - %@這句, 另一臺執(zhí)行 Block 中的內(nèi)容打印 sync之前 - %@ 這句。因為這兩條是并行的,所以打印的先后順序無所謂。
  4. 注意,高潮來了?,F(xiàn)在的情況和上一個例子一樣了。dispatch_sync同步執(zhí)行,于是它所在的線程會被阻塞,一直等到 sync 里的任務(wù)執(zhí)行完才會繼續(xù)往下。于是 sync 就高興的把自己 Block 中的任務(wù)放到 queue 中,可誰想 queue 是一個串行隊列,一次執(zhí)行一個任務(wù),所以 sync 的 Block 必須等到前一個任務(wù)執(zhí)行完畢,可萬萬沒想到的是 queue 正在執(zhí)行的任務(wù)就是被 sync 阻塞了的那個。于是又發(fā)生了死鎖。所以 sync 所在的線程被卡死了。剩下的兩句代碼自然不會打印。

GCD總結(jié)

  • 同步異步函數(shù)的作用:將任務(wù)添加到隊列中
  • 隊列作用:決定任務(wù)執(zhí)行的順序,先添加的先執(zhí)行,最后添加的任務(wù)最后執(zhí)行
  • 同步函數(shù):堵塞當(dāng)前線程;需要等待任務(wù)結(jié)束才能返回;在當(dāng)前線程中進行;無開辟線程的權(quán)限
  • 異步函數(shù):不會堵塞當(dāng)前線程;不需要等待任務(wù);有開辟線程的權(quán)限
  • 隊列和任務(wù)關(guān)系;任務(wù)放在隊列里
  • 任務(wù)跟線程關(guān)系:任務(wù)需要線程來執(zhí)行;
  • 線程跟隊列關(guān)系:一個隊列里可能有好多線程,在一個線程內(nèi)可能也有多個隊列;在某個線程里創(chuàng)建隊列,那么這個隊列是屬于這個線程的;
  • 主隊列和主線程的關(guān)系:主隊列是主線中的一個串行隊列。所有的和UI的操作(刷新或者點擊按鈕)都必須在主線程中的主隊列中去執(zhí)行,否則無法更新UI,每一個應(yīng)用程序只有唯一的一個主隊列用來update UI。如果在主線里創(chuàng)建了一個自定義的隊列,那么這個隊列也就屬于主線程的隊列。
  • 注意重點:如果在主線程里創(chuàng)建了一個自定義的隊列,且如果主線程在主隊列中被堵塞了,那么主線程會跑到這個自定義隊列里看看有沒有任務(wù),如果有就執(zhí)行。
  • 如果在主線程中創(chuàng)建自定義隊列(串行或者并行均可),在這個隊列中執(zhí)行同步任務(wù),同樣可以更新UI操作,主隊列中可以更新UI,自定義隊列也可以更新UI,但自定義隊列的更新UI的前提是在主線程中執(zhí)行同步任務(wù)

面試題: 同步主隊列死鎖原因

在主線程里,開啟同步任務(wù),并打算使用主隊列,當(dāng)開始執(zhí)行同步函數(shù)的時候,這時候發(fā)生了什么:

  1. 主隊列會把同步函數(shù)任務(wù)放到主隊列的最前面也就是最先執(zhí)行的地方
  2. 同步函數(shù)堵塞當(dāng)前的線程也就是主線程,并把block中的任務(wù)添加到主隊列,這時候block中的任務(wù)跟同步任務(wù)都在同一個線程、同一個隊列,block中的任務(wù)排在同步任務(wù)的后面,等待前面任務(wù)(同步任務(wù))的執(zhí)行。
  3. 同步函數(shù)的特點是必須等待block中的任務(wù)執(zhí)行完才能返回,但是同步任務(wù)和block中的任務(wù)都在主隊列里,根據(jù)先進先執(zhí)行(FIFO)的原則,會發(fā)生這樣的無限循環(huán)現(xiàn)象:同步任務(wù)等待block任務(wù)執(zhí)行完,但是同步任務(wù)又排在了block任務(wù)的前面,block任務(wù)不能執(zhí)行,那就等于同步任務(wù)也不能執(zhí)行完成,所以產(chǎn)生死鎖現(xiàn)象。

面試題: 同步串行隊列不會發(fā)生死鎖原因

在主線程里,開啟同步任務(wù),并自定義串行隊列,當(dāng)開始執(zhí)行同步函數(shù)的時候,這時候發(fā)生了什么:

  1. 主隊列會把同步函數(shù)任務(wù)放到主隊列的最前面也就是最先執(zhí)行的地方
  2. 同步函數(shù)堵塞當(dāng)前的線程也就是主線程,并把block中的任務(wù)添加到自定義串行隊列,這時候,在主線程也就有了兩個隊列,block中的任務(wù)跟同步任務(wù)都在同一個線程,但是不在同一個隊列。
  3. 主線程在主隊列被同步函數(shù)堵塞,會跑到它的其他隊列里,看看有沒有要執(zhí)行的任務(wù)。所以它會去自定義隊列里去執(zhí)行任務(wù)。
  4. 同步函數(shù)的特點是必須等待block中的任務(wù)執(zhí)行完才能返回,自定義隊列里的任務(wù)已被執(zhí)行完,同步函數(shù)也就執(zhí)行完返回主線程。

重點:雖然主隊列中的主線程被堵塞了,但是主線程可以去執(zhí)行其他屬于主線程的隊列的任務(wù)。

(原文鏈接)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 多線程學(xué)習(xí)筆記-GCD 我把這篇文章所用到的代碼總結(jié)到這里->GCD項目總結(jié)下載地址-GCD-wxk可以下載參考 ...
    wxkkkkk閱讀 578評論 0 2
  • 一、簡介在iOS所有實現(xiàn)多線程的方案中,GCD應(yīng)該是最有魅力的,因為GCD本身是蘋果公司為多核的并行運算提出的解決...
    MYS_iOS_8801閱讀 599評論 0 0
  • 目錄:iOS多線程(一)--pthread、NSThreadiOS多線程(二)--GCD詳解iOS多線程(三)--...
    Claire_wu閱讀 1,139評論 0 6
  • 一、基本概念 線程是用來執(zhí)行任務(wù)的,線程徹底執(zhí)行完任務(wù)A才能執(zhí)行任務(wù)B,為了同時執(zhí)行兩個任務(wù),產(chǎn)生了多線程 1、進...
    空白Null閱讀 809評論 0 3
  • 我發(fā)現(xiàn),我很愛亂想。 列了一大堆計劃,到最后發(fā)現(xiàn)很難實現(xiàn)。要么進展緩慢,看不到結(jié)果導(dǎo)致放棄;要么遇到困難,對自己懷...
    寫個好文案閱讀 410評論 0 0

友情鏈接更多精彩內(nèi)容