多線程之GCD學(xué)習(xí)整理

把GCD的資料重新整理了一下 ( ̄. ̄)

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

設(shè)計
GCD是蘋果公司為多核并行運算提出的解決方案
GCD會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)
GCD會自動利用更多的CPU內(nèi)核(比如雙核、四核)

GCD是一個替代諸如NSThread等技術(shù)的很高效和強大的技術(shù)。
GCD完全可以處理諸如數(shù)據(jù)鎖定和資源泄漏等復(fù)雜的異步編程問題。
GCD的工作原理是讓一個程序,根據(jù)可用的處理資源,安排他們在任何可用的處理器核心上平行排隊執(zhí)行特定的任務(wù)。這個任務(wù)可以是一個功能或者一個程序段。
GCD創(chuàng)建的隊列是輕量級的,蘋果聲明一個GCD的工作單元需要由15個指令組成。也就是說創(chuàng)造一個傳統(tǒng)的線程很容易的就會需要幾百條指令。
GCD中的一個任務(wù)可被用于創(chuàng)造一個被放置于隊列的工作項目或者事件源。如果一個任務(wù)被分配到一個事件源,那么一個由功能或者程序塊組成的工作單元會被放置于一個適當(dāng)?shù)年犃兄小LO果公司認(rèn)為GCD相比于普通的一個接一個的執(zhí)行任務(wù)的方式更為有效率。

任務(wù)(block ):要執(zhí)行的操作


任務(wù)是由block封裝的

dispatch_block_t task

void(^myBlock)()=^{
//任務(wù)/想要做的事情 
}

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


隊列不是線程 隊列中存放的任務(wù)最后都要由線程來執(zhí)行
隊列的原則:(FIFO) First In First Out 先進先出 后進后出

隊列類型
串行隊列:Serial Dispatch Queue
存放順序執(zhí)行的任務(wù)
線程池只提供一個線程用來執(zhí)行任務(wù)
一個任務(wù)執(zhí)行完畢 在執(zhí)行下一個任務(wù)
并發(fā)隊列:Concurrent Dispatch Queue
存放想要同時并發(fā)執(zhí)行的任務(wù) 可以開啟多線程 具體數(shù)量由底層GCD負(fù)責(zé)
線程池可以提供多個線程來執(zhí)行任務(wù) 具體數(shù)量由底層GCD負(fù)責(zé)

?

我以前在這里打了個問號 回頭卻忘了有什么疑問 銘記教訓(xùn) 以后 有疑問 就把自己的疑問寫清楚

并發(fā)執(zhí)行 性能高 執(zhí)行順序不固定 費電因為絕大多數(shù)會使用全局隊列,全局隊列本身就是并發(fā)隊列

dispatch_queue_create
//串行隊列
dispatch_queue_t  SerialQueue;
//并發(fā)隊列
dispatch_queue_t  ConcurrentQueue;
//后面這個參數(shù)可以不寫的  默認(rèn)填NULL就是串行
SerialQueue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
ConcurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);

系統(tǒng)隊列

系統(tǒng)提供了兩個隊列
** 主隊列**:dispatch_get_main_queue
屬于串行隊列
負(fù)責(zé)調(diào)度主線程度的任務(wù),沒有辦法開辟新的線程。
所以主隊列下的任務(wù)不管是異步任務(wù)還是同步任務(wù)都不會開辟線程
任務(wù)只會在主線程順序執(zhí)行。
主隊列異步任務(wù):現(xiàn)將任務(wù)放在主隊列中,但是不是馬上執(zhí)行,等到主隊列中的其它所有除我們使用代碼添加到主隊列的任務(wù)的任務(wù)都執(zhí)行完畢之后才會執(zhí)行我們使用代碼添加的任務(wù)。
主隊列同步任務(wù):容易阻塞主線程,所以不要這樣寫。原因:我們自己代碼任務(wù)需要馬上執(zhí)行,但是主線程正在執(zhí)行代碼任務(wù)的方法體,因此代碼任務(wù)就必須等待,而主線程又在等待代碼任務(wù)的完成好去完成下面的任務(wù),因此就形成了相互等待。整個主線程就被阻塞了。

全局隊列dispatch_get_global_queue
屬于并發(fā)隊列
一般情況下 并發(fā)任務(wù)都可以放在全局并發(fā)隊列中

全局隊列和并發(fā)隊列的區(qū)別:
1 全局隊列沒有名字,但是并發(fā)隊列有名字。有名字可以便于查看系統(tǒng)日志
2 全局隊列是所有應(yīng)用程序共享的。
3 在mrc的時候,全局隊列不用手動釋放,但是并發(fā)隊列需要。

dispatch_queue_t
dispatch_queue_t mymainQueue;
dispatch_queue_t myglobalQueue;
mymainQueue = dispatch_get_main_queue();
myglobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

/**參數(shù)說明:
參數(shù)1:代表該任務(wù)的優(yōu)先級,默認(rèn)寫0就行,不要使用系統(tǒng)提供的枚舉類型,因為ios7和ios8的枚舉數(shù)值不一樣,使用數(shù)字可以通用。
DISPATCH_QUEUE_PRIORITY_HIGH  2
DISPATCH_QUEUE_PRIORITY_DEFAULT 0
DISPATCH_QUEUE_PRIORITY_LOW (-2)
DISPATCH_QUEUE_PRIORITY_BACKGROUND (-32768)
參數(shù)2:蘋果保留關(guān)鍵字,一般寫0或NULL
所以也可以寫為myglobalQueue = dispatch_get_global_queue(0, 0);
*/  

同步異步


sync同步運行
如果是同步執(zhí)行 隊列會等任務(wù)結(jié)束后 再調(diào)度后續(xù)的任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
async 異步運行
dispatch_sync(dispatch_queue_t queue, ^(void)block )
*dispatch_block_t 就是無返回值 無參數(shù)的block

常用用法介紹


1.經(jīng)典用法(子線程下載(耗時操作),主線程刷新UI)

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

2.GCD的延時執(zhí)行
延時是延時任務(wù)加入到隊列的時間 不是延時任務(wù)執(zhí)行的時間

//1.延時不是一定時間后執(zhí)行相應(yīng)的任務(wù),而是一定時間后,將任務(wù)加入到隊列中(隊列里面再分配執(zhí)行的時間)    
//2.主線程 RunLoop 1/60秒檢測時間,追加的時間范圍 3s~(3+1/60)s   
//3.在哪個線程執(zhí)行,跟隊列類型有關(guān)

//dispatch_after(一定時間后,將執(zhí)行的操作加入到隊列中)
//dispatch_time_t when 指定時間        
/* NSEC_PER_SEC 秒     
* NSEC_PER_MSEC 毫秒     
* NSEC_PER_USEC 微秒     
*/    
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);    
dispatch_queue_t que = dispatch_queue_create("h", DISPATCH_QUEUE_SERIAL);
//1.第一種用法       
dispatch_after(time, dispatch_get_main_queue(), ^{        
   NSLog(@"第一種延時 code to be executed on the main queue after delay");   
});        
//2.第二種用法   
//dispatch_function_t work 執(zhí)行的c語言方法   
dispatch_after_f(time, que, NULL, fun1);

//3.第三種用法    
dispatch_after(time, que, ^{ 
   NSLog(@"第三種延時 code to be executed on the main queue after delay");    
});         

------代表方法外的分割-----
void fun1(){    
   NSLog(@"第二種延時 code to be executed on the main queue after delay");
} 

3.異步執(zhí)行:

//dispatch_async +全局并發(fā)隊列(可以開啟多條線程)
//dispatch_async +自己創(chuàng)建的串行隊列(開啟一條線程)
dispatch_async(dispatch_get_global_queue(0, 0), ^{
      // something
 });
 
 dispatch_async(dispatch_get_main_queue(), ^{
      // 主隊列異步
 });
 一次性執(zhí)行:
static dispatch_once_t onceToken;
 dispatch_once(&onceToken, ^{
     // code to be executed once
 });

4.一次性執(zhí)行和多次執(zhí)行

/*
一次執(zhí)行:dispatch_once
 作用:在多線程的情況下,同樣能夠保證指定的代碼塊只被執(zhí)行一次
 快捷鍵:
 應(yīng)用場景:單例設(shè)計模式
 */
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    NSLog(@"塊代碼只能執(zhí)行一次");
});
/*
多次執(zhí)行:dispatch_apply
以指定的次數(shù)將指定的Block加入到指定的隊列中 并等待隊列中操作全部完成.
當(dāng)指定隊列為串行時 有序單線程執(zhí)行 
當(dāng)指定隊列為并發(fā)隊列時 多線程無序執(zhí)行
*/
dispatch_queue_t q1 = dispatch_queue_create("a1", DISPATCH_QUEUE_CONCURRENT);/
dispatch_apply(3, q1, ^(size_t index) {
    NSLog(@"重要的事情說三遍 第%zu遍 %@",index,[NSThread currentThread]);
});

**5.dispatch_group分組 **

/**
 作用:所有任務(wù)執(zhí)行完成之后,統(tǒng)一通知用戶 
可以實現(xiàn)監(jiān)聽一組任務(wù)是否完成 完成后得到通知執(zhí)行其他的操作 
不包括延時任務(wù) 因為延時是任務(wù)放進隊列的時間 
 */
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t q1 = dispatch_queue_create("a1", DISPATCH_QUEUE_SERIAL);
dispatch_block_t t1 = ^{
        NSLog(@"任務(wù)1");
    };
dispatch_async(q1, t1);
dispatch_group_async(group, q1, ^{
        NSLog(@"group1");
    });
dispatch_group_async(group, q1, ^{
        NSLog(@"group2");
    });
dispatch_group_notify(group, q1, ^{
        NSLog(@"end");
    });

6.dispatch_barrier_async的使用

/*
dispatch_barrier_async是在前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,而且它后面的任務(wù)等它執(zhí)行完成之后才會執(zhí)行
*/
 dispatch_queue_t queue = dispatch_queue_create("aa", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"dispatch_async1");
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"dispatch_async2");
    });
    dispatch_barrier_async(queue, ^{
        
        NSLog(@"dispatch_barrier_async1");
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_barrier_async2");
        
    });
    dispatch_async(queue, ^{
        NSLog(@"dispatch_async3");
    });  

7、dispatch_set_target_queue
使用dispatch_set_target_queue將多個串行的queue指定到了同一目標(biāo),那么著多個串行queue在目標(biāo)queue上就是同步執(zhí)行的,不再是并行執(zhí)行。

Important
If you modify the target queue for a queue, you must be careful to avoid creating cycles in the queue hierarchy.

dispatch_queue_t tq = dispatch_queue_create("tq",DISPATCH_QUEUE_SERIAL); 
dispatch_queue_t q1 = dispatch_queue_create("q1", DISPATCH_QUEUE_SERIAL); 
dispatch_queue_t q2 = dispatch_queue_create("q2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t q3 = dispatch_queue_create("q3", DISPATCH_QUEUE_SERIAL);
    
    dispatch_set_target_queue(q1, tq);
    dispatch_set_target_queue(q2, tq);
    dispatch_set_target_queue(q3, tq);
  
    dispatch_async(q1, ^{
        NSLog(@"1 in %@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:3.f];
        NSLog(@"1 out%@",[NSThread currentThread]);
    });
   
    dispatch_async(q2, ^{
        NSLog(@"2 in %@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.f];
//        dispatch_suspend(queue1);
        NSLog(@"2 out %@",[NSThread currentThread]);
    });
    dispatch_async(q3, ^{
        NSLog(@"3 in %@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"3 out %@",[NSThread currentThread]);
    }); 

8.信號量 dispatch_semaphore

dispatch_semaphore是GCD用來同步的一種方式 相關(guān)函數(shù)有三個

dispatch_semaphore_create 
dispatch_semaphore_signal 
dispatch_semaphore_wait 

(1)dispatch_semaphore_create
dispatch_semaphore_t dispatch_semaphore_create(long value);
傳入的參數(shù)為long,輸出一個dispatch_semaphore_t類型且值為value的信號量
*這里的傳入的參數(shù)value必須大于或等于0 否則dispatch_semaphore_create會返回NULL。

(2)dispatch_semaphore_signal
long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
這個函數(shù)會使傳入的信號量dsema的值加1
返回值為long類型
當(dāng)返回值為0時表示當(dāng)前并沒有線程等待其處理的信號量,其處理的信號量的值加1即可。
當(dāng)返回值不為0時,表示其當(dāng)前有一個或多個線程等待其處理的信號量,并且該函數(shù)喚醒了一個等待的線程(當(dāng)線程有優(yōu)先級時,喚醒優(yōu)先級最高的線程;否則隨機喚醒)。

(3) dispatch_semaphore_wait
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
這個函數(shù)會使傳入的信號量dsema的值減1;
如果dsema信號量的值大于0,該函數(shù)所處線程就繼續(xù)執(zhí)行下面的語句,并且將信號量的值減1;
如果desema的值為0,那么這個函數(shù)就阻塞當(dāng)前線程等待timeout(注意timeout的類型為dispatch_time_t 不能直接傳入整形或float型數(shù))
如果等待的期間desema的值被dispatch_semaphore_signal函數(shù)加1了,且該函數(shù)(即dispatch_semaphore_wait)所處線程獲得了信號量,那么就繼續(xù)向下執(zhí)行并將信號量減1。
如果等待期間沒有獲取到信號量或者信號量的值一直為0,那么等到timeout時,其所處線程自動執(zhí)行其后語句。
設(shè)置timeout時,比較有用的兩個宏:
DISPATCH_TIME_NOW 表示當(dāng)前;
DISPATCH_TIME_FOREVER 表示遙遠的未來;
一般可以直接設(shè)置timeout為這兩個宏其中的一個,或者自己創(chuàng)建一個dispatch_time_t類型的變量。
返回值為long型。當(dāng)其返回0時表示在timeout之前,該函數(shù)所處的線程被成功喚醒。當(dāng)其返回不為0時,表示timeout發(fā)生。

dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [NSMutableArray array];
    
    for (int index = 0; index < 10000; index++) {
        dispatch_async(queue, ^(){  
            dispatch_semaphore_wait(semaphore, timeout); 

            NSLog(@"addd :%d", index);
            [array addObject:[NSNumber numberWithInt:index]];
            
            dispatch_semaphore_signal(semaphore);
        });
    }

GCD 死鎖


-(void)viewDidLoad { [super viewDidLoad]; NSLog(@"1"); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2"); }); NSLog(@"3"); }
我們來一步一步解析。首先dispatch_sync 是同步的,它會造成一個后果那就是阻塞主線程,并且會一直會等待block,而block放入到了主線程隊列dispatch_get_main_queue()中,這是一個FIFA隊列,就是先進先出的隊列。他在等待主線程執(zhí)行。那么一目了然了。我用偽代碼敲出來大家就知道了
MainThread { dispatch_get_main_queue(){ syncblock(); } }
MainThread等待dispatch_sync,dispatch_sync等待blockblock等待mainquen,maiden等待MainThread,而MainThread等待dispatch_sync。這樣就形成了一個死循環(huán)。俗稱DeadLock死鎖。

NSLog(@"1"); dispatch_async(dispatch_get_main_queue(), ^{ while (1) { NSLog(@"2"); } }); dispatch_async(dispatch_get_main_queue(), ^{ while (1) { NSLog(@"3"); } }); NSLog(@"4");
結(jié)果大家一運行就知道,
為什么第一個只輸出1和4 和2。我們來解析問題的代碼
這里面有兩部異步block但是放到了主線程隊列里面,但是block里面執(zhí)行的是一個 while (1) 的死循環(huán)
我們一步一步解析。 主線程隊列是個FIFO 也就是先進先出,先進的完了才執(zhí)行第二個。而當(dāng)
^{ while (1) { NSLog(@"2"); } }
放入到主線程隊列后,它就永遠執(zhí)行不完,永遠不會退出,所以
^{ while (1) { NSLog(@"3"); }
這個只能永遠的等待,而這兩個block又是異步的不會阻塞主線程所以主線程的輸出依然木有問題。


關(guān)于命名

所有帶t結(jié)尾的 都是用來聲明變量的對象
所有帶f結(jié)尾的 都是面向C的function


GCD官方文檔OC版
GCD學(xué)習(xí)總結(jié)
Grand Central Dispatch
SDK源碼解讀系列:《iOS與OSX多線程和內(nèi)存管理》書摘之GCD內(nèi)部實現(xiàn)(一)

最后編輯于
?著作權(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)容

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