iOS 多線程之GCD

1 GCD簡述

Apple源碼--Dispatch

Grand Central Dispatch(GCD)Apple開發(fā)的一個多核編程的較新的解決方法.它主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對稱多處理系統(tǒng).它是一個在線程池模式的基礎(chǔ)上執(zhí)行的并發(fā)任務(wù).在Mac OS X 10.6雪豹中首次推出,也可在iOS 4及以上版本使用.

GCD優(yōu)點

  • GCD可用于多核的并行運算
  • GCD會自動利用更多的CPU內(nèi)核(比如雙核、四核)
  • GCD會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)
  • 程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼

2 GCD任務(wù)和隊列

任務(wù)

任務(wù): 執(zhí)行操作的意思,就是說你在線程中執(zhí)行的那段代碼.在GCD中是放在block中的.執(zhí)行任務(wù)有兩種方式同步執(zhí)行異步執(zhí)行

  • 同步執(zhí)行(sync):
    (1) 同步添加任務(wù)到指定的隊列中,在添加的任務(wù)執(zhí)行結(jié)束之前,會一直等待,直到隊列里面的任務(wù)完成之后再繼續(xù)執(zhí)行
    (2) 能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
  • 異步執(zhí)行(async):
    (1) 異步添加任務(wù)到指定的隊列中,它不會做任何等待,可以繼續(xù)執(zhí)行任務(wù)
    (2) 可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力
    注意: <u>異步執(zhí)行(async)雖然具有開啟新線程的能力,但是并不一定開啟新線程.這跟任務(wù)所指定的隊列類型有關(guān)(下面會講)</u>

任務(wù)的創(chuàng)建分為:同步任務(wù)dispatch_sync和異步任務(wù)dispatch_async

dispatch_sync(queue, ^{
    // 同步執(zhí)行任務(wù)
    // code snippet
});
dispatch_async(queue, ^{
    // 異步執(zhí)行任務(wù)
    // code snippet
});

隊列(Dispatch Queue)

隊列:隊列指執(zhí)行任務(wù)的等待隊列,即用來存放任務(wù)的隊列.隊列是一種特殊的線性表,采用FIFIO(先進先出)的原則.

image

GCD隊列分為兩種:串行隊列并行隊列
主要區(qū)別: 執(zhí)行順序不同,以及開啟線程數(shù)不同.

  • 串行隊列(Serial Dispatch Queue):每次只有一個任務(wù)被執(zhí)行.讓任務(wù)一個接著一個地執(zhí)行.(只開啟一個線程,一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))
  • 并發(fā)隊列(Concurrent Dispatch Queue):可以讓多個任務(wù)并發(fā)(同時)執(zhí)行.(可以開啟多個線程,并且同時執(zhí)行任務(wù))

注意:<u>并發(fā)隊列 的并發(fā)功能只有在異步(dispatch_async)方法下才有效.其他線程下,串行執(zhí)行任務(wù)</u>

image

隊列的創(chuàng)建:dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);

  • 參數(shù)0: 隊列的唯一標(biāo)識符,隊列的名稱推薦使用應(yīng)用程序id這種逆序全程域名
  • 參數(shù)1: 用來識別是串行隊列還是并發(fā)隊列 (DISPATCH_QUEUE_SERIAL, DISPATCH_QUEUE_CONCURRENT)
// 串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_SERIAL);
// 并發(fā)隊列
dispatch_queue_t concurrentlQueue = dispatch_queue_create("com.appleid.functionB", DISPATCH_QUEUE_CONCURRENT);
// 主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 全局并發(fā)隊列 (參數(shù)0: 填寫默認 , 參數(shù)1: 填寫0)
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

3 隊列和同步,異步任務(wù)組合

區(qū)別 并發(fā)隊列 串行隊列 主隊列
同步(sync) 沒有開啟新線程,串行執(zhí)行任務(wù) 沒有開啟新線程,串行執(zhí)行任務(wù) 死鎖卡住不執(zhí)行
異步(async) 有開啟新線程,并發(fā)執(zhí)行任務(wù) 有開啟新線程(1條),串行執(zhí)行任務(wù) 沒有開啟新線程,串行執(zhí)行任務(wù)

4 GCD方法

4.1 dispatch_after:延時執(zhí)行方法

主要:<u>dispatch_after方法并不是在指定時間之后才開始執(zhí)行處理,而是在指定時間之后將任務(wù)追加到主隊列中.準(zhǔn)確來說,這個時間并不是絕對準(zhǔn)確的,但想要大致延遲執(zhí)行任務(wù),dispatch_after 方法是很有效的</u>

- (void)after {
    NSLog(@"當(dāng)前線程%@", [NSThread currentThread]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"線程after:%@", [NSThread currentThread]);  // 打印當(dāng)前線程
    });
}

4.2 dispatch_once:只執(zhí)行一次

在創(chuàng)建單例、或者有整個程序運行過程中只執(zhí)行一次的代碼時,就可以使用dispatch_once方法.dispatch_once方法能保證某段代碼在程序運行過程中只被執(zhí)行1次,并且即使在多線程的環(huán)境下, dispatch_once也可以保證線程安全.

- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只執(zhí)行一次, 默認線程安全
        // code snippet
    });
}

4.3 dispatch_barrier_async: 柵欄函數(shù)

Apple官方文檔

對這個函數(shù)的調(diào)用總是在block被提交之后立即返回,并且從不等block待被調(diào)用.當(dāng)barrier block到達私有并發(fā)隊列的前端時,它不會立即執(zhí)行.相反,隊列將等待,直到當(dāng)前執(zhí)行的塊完成執(zhí)行.此時,barrier block自己執(zhí)行.在barrier block之后提交的任何block都不會執(zhí)行,直到barrier block完成.
您指定的隊列應(yīng)該是您自己使用dispatch_queue_create函數(shù)創(chuàng)建的并發(fā)隊列.如果傳遞給此函數(shù)的隊列是一個串行隊列或一個全局并發(fā)隊列,則此函數(shù)的行為與dispatch_async函數(shù)類似.

image
  • dispatch_barrier_sync

- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)1, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任務(wù)2, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)3, %@", [NSThread currentThread]);
    });

    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:2- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)1, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任務(wù)2, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)3, %@", [NSThread currentThread]);
    });

    
    dispatch_barrier_sync(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)4 barrier, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)5, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)6, %@", [NSThread currentThread]);
    });
    
    NSLog(@"任務(wù)7, %@", [NSThread currentThread]);
}];
        NSLog(@"barrier任務(wù)4, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)5, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)6, %@", [NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:

任務(wù)2, <NSThread: 0x139040570>{number = 4, name = (null)}
任務(wù)3, <NSThread: 0x139458a90>{number = 6, name = (null)}
任務(wù)1, <NSThread: 0x139043ce0>{number = 5, name = (null)}
任務(wù)4 barrier, <NSThread: 0x137e0b8d0>{number = 1, name = main}
任務(wù)7, <NSThread: 0x137e0b8d0>{number = 1, name = main}
任務(wù)5, <NSThread: 0x139043ce0>{number = 5, name = (null)}
任務(wù)6, <NSThread: 0x139458a90>{number = 6, name = (null)}
  • dispatch_barrier_async
- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)1, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任務(wù)2, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)3, %@", [NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)4 barrier, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)5, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)6, %@", [NSThread currentThread]);
    });

    NSLog(@"任務(wù)7, %@", [NSThread currentThread]);
}

執(zhí)行結(jié)果:

任務(wù)7, <NSThread: 0x10360aea0>{number = 1, name = main}
任務(wù)2, <NSThread: 0x1035a4f90>{number = 3, name = (null)}
任務(wù)1, <NSThread: 0x105e79130>{number = 6, name = (null)}
任務(wù)3, <NSThread: 0x1036afae0>{number = 5, name = (null)}
任務(wù)4 barrier, <NSThread: 0x1036afae0>{number = 5, name = (null)}
任務(wù)5, <NSThread: 0x1036afae0>{number = 5, name = (null)}
任務(wù)6, <NSThread: 0x105e79130>{number = 6, name = (null)}

4.4 dispatch_apply:快速迭代(高效for循環(huán))

Apple官方文檔

此函數(shù)將多個調(diào)用的block提交給調(diào)度隊列,并等待任務(wù)block的所有迭代完成后再返回.如果目標(biāo)隊列是由dispatch_get_global_queue返回的并發(fā)隊列,則可以并發(fā)調(diào)用該block,因此它必須是reentrant安全的.在并發(fā)隊列中使用此函數(shù)可以作為一種有效的并行for循環(huán).
迭代的當(dāng)前索引被傳遞給block的每次調(diào)用.

- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // dispatch_apply是同步的
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"同步index:%zu %@", index, [NSThread currentThread]);
    });

    // 如果想異步,包裝一層
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_apply(10, queue, ^(size_t index) {
            NSLog(@"異步index:%zu %@", index, [NSThread currentThread]);
        });
    });
}

4.5 dispatch_group: 隊列組

  • 調(diào)用隊列組的dispatch_group_async先把任務(wù)放到隊列中,然后將隊列放入隊列組中.或者使用隊列組的dispatch_group_enter、dispatch_group_leave組合來實現(xiàn)
  • 調(diào)用隊列組的dispatch_group_notify回到指定線程執(zhí)行任務(wù).或者使用dispatch_group_wait回到當(dāng)前線程繼續(xù)向下執(zhí)行(會阻塞當(dāng)前線程)
  • dispatch_group_notify: 監(jiān)聽group中任務(wù)的完成狀態(tài),當(dāng)所有的任務(wù)都執(zhí)行完成后,追加任務(wù)到group中,并執(zhí)行任務(wù)
- (void)group {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)1, %@", [NSThread currentThread]);
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)2, %@", [NSThread currentThread]);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的異步任務(wù) 1、任務(wù) 2 都執(zhí)行完畢后,回到主線程執(zhí)行下邊任務(wù)
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)3, %@", [NSThread currentThread]);
        NSLog(@"group任務(wù)完成");
    });
}

執(zhí)行結(jié)果:

任務(wù)2, <NSThread: 0x281f87280>{number = 5, name = (null)}
任務(wù)1, <NSThread: 0x281fa0c00>{number = 8, name = (null)}
任務(wù)3, <NSThread: 0x281fc4d80>{number = 1, name = main}
group任務(wù)完成
  • dispatch_group_wait:暫停當(dāng)前線程(阻塞當(dāng)前線程),等待指定的group中的任務(wù)執(zhí)行完成后,才會往下繼續(xù)執(zhí)行
- (void)group {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)1, %@", [NSThread currentThread]);
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)2, %@", [NSThread currentThread]);
    });

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group任務(wù)完成");
 }

執(zhí)行結(jié)果:

任務(wù)2, <NSThread: 0x2817f9540>{number = 4, name = (null)}
任務(wù)1, <NSThread: 0x2817ff9c0>{number = 6, name = (null)}
group任務(wù)完成
  • dispatch_group_enter(), dispatch_group_leave
- (void)group1 {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)1, %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)2, %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的異步任務(wù) 1、任務(wù) 2 都執(zhí)行完畢后,回到主線程執(zhí)行下邊任務(wù)
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)3, %@", [NSThread currentThread]);
        NSLog(@"group任務(wù)完成");
    });
}

執(zhí)行結(jié)果:

任務(wù)2, <NSThread: 0x281df3e40>{number = 4, name = (null)}
任務(wù)1, <NSThread: 0x281de3f80>{number = 7, name = (null)}
任務(wù)3, <NSThread: 0x281db0d80>{number = 1, name = main}
group任務(wù)完成

4.6 dispatch_semaphore: 信號量

dispatch_semaphoreGCD中的信號量,持有計數(shù)的信號, dispatch Semaphore中,使用計數(shù)來完成這個功能,計數(shù)小于0時等待,不可通過.計數(shù)為0或大于0時可通過.

主要使用:

  • 保持線程同步,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)
  • 保證線程安全,為線程加鎖

dispatch_semaphore三個方法:

  • dispatch_semaphore_create: 創(chuàng)建一個semaphore并初始化信號的總量
  • dispatch_semaphore_signal: 發(fā)送一個信號,信號計數(shù) + 1
  • dispatch_semaphore_wait: 可以使總信號量 - 1,信號總量小于0時就會一直等待(阻塞所在線程),否則就可以正常執(zhí)行
- (void)semaphor {
    
    NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務(wù)1, %@", [NSThread currentThread]);

        dispatch_semaphore_signal(semaphore);
    });
    NSLog(@"當(dāng)前線程1:%@", [NSThread currentThread]);

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"任務(wù)完成");

}

執(zhí)行結(jié)果:

當(dāng)前線程:<NSThread: 0x282784d80>{number = 1, name = main}
當(dāng)前線程1:<NSThread: 0x282784d80>{number = 1, name = main}
任務(wù)1, <NSThread: 0x2827d6cc0>{number = 6, name = (null)}
任務(wù)完成

從打印結(jié)果可知執(zhí)行流程為:

  • semaphore開始計數(shù)為0
  • 異步任務(wù)加入隊列之后,不等待繼續(xù)執(zhí)行, 執(zhí)行到dispatch_semaphore_wait方法, 信號量計數(shù)- 1-1小于0,當(dāng)前線程進入等待狀態(tài)
  • 任務(wù)1執(zhí)行開始執(zhí)行, 執(zhí)行完成后,執(zhí)行dispatch_semaphore_signal,信號量計數(shù)+ 10,阻塞線程恢復(fù)繼續(xù)執(zhí)行

完整代碼見GitHub->多線程(附大廠面試講解)


如有不足之處,歡迎予以指正, 如果感覺寫的不錯,記得給個贊呦!

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

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