iOS多線程——GCD

在iOS中,實現多線程的方式有很多,比如GCD,NSOperation,NSThread等等,但是一直對線程的概念模糊,今天就根據代碼例子來了解iOS中GCD的用法和原理。

GCD是和block緊密相連的,所以最好先了解下block。
GCD是C level的函數,這意味著它也提供了C的函數指針作為參數,方便了C程序員。

下面首先來看GCD的使用:
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
async表明異步運行,block代表的是你要做的事情,queue則是你把任務交給誰來處理了。(除了async,還有sync,delay,本文以async為例)。

dispatch隊列不支持cancel(取消),沒有實現dispatch_cancel()函數,不像NSOperationQueue,不得不說這是個小小的缺憾。

之所以程序中會用到多線程是因為程序往往會需要讀取數據,然后更新UI。為了良好的用戶體驗,讀取數據的操作會傾向于在后臺運行,這樣以避免阻塞主線程。GCD里就有三種queue來處理。

GCD Queue分為三種:

  • The main queue:主隊列,主線程就是在個隊列中,系統默認就有一個串行隊列
  • Global queues: 全局并發隊列
  • 用戶隊列:是用函數dispatch_queue_create創建的自定義隊列

而用戶隊列又分為下面兩種:
(1)Serial Dispatch Queue
線程池只提供一個線程用來執行任務,所以后一個任務必須等到前一個任務執行結束才能開始。(DISPATCH_QUEUE_SERIAL)

(2)Concurrent Dispatch Queue
線程池提供多個線程來執行任務,所以可以按序啟動多個任務并發執行。(DISPATCH_QUEUE_CONCURRENT)

關于同步和異步

  • dispatch_sync
    則調用用 dispatch_sync的線程會等dispatch_sync的對內容執行完再繼續執行。dispatch_sync函數不會立即返回,即阻塞當前線程,等待block同步執行完成。

  • dispatch_async
    調用dispatch_async的線程不會的等dispatch_async的內容,自己繼續執行。dispatch_async函數會立即返回, block會在后臺異步執行。

關于并發和并行

  • 并行:是多個任務在同一個時間片段內同時進行,比如說你一邊吃飯一邊打電話一遍看電視

  • 并發:是多個任務在同一個時間的點上可以切換進行,比如說你先吃著飯,然后電話來了,停下吃飯立馬打電話,打完后立馬切換到看電視,這個你在同一個線條內只有一個任務在執行

圖示關于并發和并行

并行
并發

下面就以代碼實例來分析GCD用法

實例一:DISPATCH_QUEUE_SERIAL串行隊列

/**
 DISPATCH_QUEUE_SERIAL是每次運行一個任務,可以添加多個,執行次序FIFO。
 **/
- (void)test1
{
    NSDate *date = [NSDate date];
    
    NSString *daStr = [date description];
    
    const char *queueName = [daStr UTF8String];
    
    //DISPATCH_QUEUE_SERIAL等同于NULL
    dispatch_queue_t myQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL);
    
    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(addTime) userInfo:nil repeats:YES];
    
    //在main_queue中異步將任務放到myQueue隊列里
    //放入myQueue的順序是按代碼的執行順序:任務一,任務二,任務三(因為main_queue是順序執行)
    //在myQueue取出的順序是按照FIFO的順序
    //因為這是一個串行的隊列,所以取出后的任務是一個接著一個執行的
    
    
    //執行結果
    //[NSThread sleepForTimeInterval:6]~~~~~~~~6
    //[NSThread sleepForTimeInterval:3]~~~~~~~~9
    //[NSThread sleepForTimeInterval:12]~~~~~~~~21

    
    //任務一:
    dispatch_async(myQueue, ^{
        
        [NSThread sleepForTimeInterval:6];
        
        NSLog(@"[NSThread sleepForTimeInterval:6]~~~~~~~~%d", t);
        
    });
    
    //任務二:
    dispatch_async(myQueue, ^{
        
        [NSThread sleepForTimeInterval:3];
        
        NSLog(@"[NSThread sleepForTimeInterval:3]~~~~~~~~%d", t);
        
    });
    
    //任務三:
    dispatch_async(myQueue, ^{
        
        [NSThread sleepForTimeInterval:12];
        
        NSLog(@"[NSThread sleepForTimeInterval:12]~~~~~~~~%d", t);
        
    });
}

-(void)addTime {
    
    ++t;
}

實例二:全局的并發隊列dispatch_get_global_queue

/**
 可以同時運行多個任務,每個任務的啟動時間是按照加入queue的順序,結束的順序依賴各自的任務.
 **/
- (void)test2
{
    dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
    //在main_queue中異步將任務放到myQueue
    //放入myQueue的順序是:任務一,任務二,任務三
    //在myQueue中取出的順序是FIFO的原則:任務一,任務二,任務三
    //取出的任務放到線程里執行,因為這是一個并行的隊列,所以任務可以同時運行
    
    //執行的結果
    //執行的結果可以是 1 2 3 的隨機組合
    
    //任務一:
    dispatch_async(myQueue, ^{
        
        NSLog(@"~~~~~~~~~1");
        
    });
    
    //任務二:
    dispatch_async(myQueue, ^{
        
        NSLog(@"~~~~~~~~~2");
        
    });
    
    //任務三:
    dispatch_async(myQueue, ^{
        
        NSLog(@"~~~~~~~~~3");
        
    });
}

實例三:DISPATCH_QUEUE_SERIAL串行隊列

/**
 串行隊列的異步任務:使用一個子線程依次執行。
  對比一下dispatch_async和dispatch_sync輸出的i的順序和線程的地址
 **/
- (void)test3
{
    dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
    
    for (int i = 0; i < 3; i++) {
        
        //在main_queue中將i=0,1,2的任務異步加入queue隊列中
        //加入queue的順序是:i=0, i=1, i=2
        //所以在queue中取出的順序是:i=0,i=1,i=2
        //因為queue是同步隊列,所以三個任務放到同一個線程中依次執行
        
        //運行結果
        //~~~i=0~~~~<NSThread: 0x600000076940>{number = 3, name = (null)}
        //<NSThread: 0x600000068d00>{number = 1, name = (null)}
        //~~~i=1~~~~<NSThread: 0x600000076940>{number = 3, name = (null)}
        //<NSThread: 0x600000068d00>{number = 1, name = (null)}
        // ~~~i=2~~~~<NSThread: 0x600000076940>{number = 3, name = (null)}
        //<NSThread: 0x600000068d00>{number = 1, name = (null)}

        //當是使用DISPATCH_QUEUE_SERIAL和dispatch_sync同步的時候,三個任務執行的線程就是當前的mainThread中執行的

        dispatch_async(queue, ^{
            
            NSLog(@"~~~i=%d~~~~%@", i, [NSThread currentThread]);
            
            NSLog(@"%@",[NSThread mainThread]);
            
        });
    }
}

實例四:DISPATCH_QUEUE_CONCURRENT并發隊列

/**
 并行隊列的異步任務:使用多個子線程無序執行,一般任務較少時幾個任務就開幾個線程,較多時則開部分線程。
 應用:一系列的異步任務沒有先后順序,結束無序
 對比一下dispatch_async和dispatch_sync輸出的線程的地址
 **/
- (void)test4
{
    
    dispatch_queue_t queue = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
    
    //在main_queue中異步的將i=0,i=1,i=2的任務加入到queue中
    //加入queue的順序是i=0,i=1,i=2
    //在queue中取出的順序是i=0,i=1,i=2
    //因為queue是并發隊列,所以有多條的線程來執行三個任務,thread的執行的順序不定
    
    //當使用dispatch_sync時,執行的順序又成了i=0,i=1,i=2
    
    for (int i = 0; i < 3; i++) {
        
        dispatch_async(queue, ^{
            
            NSLog(@"~~~i=%d~~~~%@", i, [NSThread currentThread]);
            
            NSLog(@"%@",[NSThread mainThread]);
            
        });
    }
}

實例五:dispatch_group_t組隊列

/**
 dispatch_group_async可以實現監聽一組任務是否完成,完成后得到通知執行其他的操作。
 dispatch_group_async會監聽最終的任務完成后,并通知一個線程
 這個方法很有用,比如你執行三個下載任務,當三個任務都下載完成后你才通知界面說完成了。
注意這里不是監聽queue里所有的任務完成,而是添加到組里的任務,這個任務是在這個queue里,同時也在這個組里,組里所有的任務的完成并不代表queue里所有的任務的完成。
 下面是一段例子代碼:
 注意:當queue是global(或者DISPATCH_QUEUE_CONCURRENT)隊列和DISPATCH_QUEUE_SERIAL隊列時線程的區別
 **/
- (void)test5
{
    
    //dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_queue_t queue = dispatch_queue_create("queue3", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"task1~~~~currentThread=%@~~~~~~mainThread%@", [NSThread currentThread], [NSThread mainThread]);
        
    });
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"task2~~~~currentThread=%@~~~~~~mainThread%@", [NSThread currentThread], [NSThread mainThread]);
        
    });
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"task3~~~~currentThread=%@~~~~~~mainThread%@", [NSThread currentThread], [NSThread mainThread]);
        
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        
        NSLog(@"回到主線程");
        
    });
}

實例六:dispatch_apply重復

/**
 dispatch_apply:執行某個代碼片段N次
 
 重復執行block,需要注意的是這個方法是同步返回,也就是說等到所有block執行完畢才返回,如需異步返回則嵌套在dispatch_async中來使用。
 多個block的運行是否并發或串行執行也依賴queue的是否并發或串行。
 
 dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
 運行結果:
 array[0]~~~~~~~~~~~~~~0
 array[3]~~~~~~~~~~~~~~3
 array[2]~~~~~~~~~~~~~~2
 array[1]~~~~~~~~~~~~~~1
 array[4]~~~~~~~~~~~~~~4
 array[5]~~~~~~~~~~~~~~5
 
 dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
 運行結果:
 array[0]~~~~~~~~~~~~~~0
 array[1]~~~~~~~~~~~~~~1
 array[2]~~~~~~~~~~~~~~2
 array[3]~~~~~~~~~~~~~~3
 array[4]~~~~~~~~~~~~~~4
 array[5]~~~~~~~~~~~~~~5
 **/
- (void)test6
{
    
    NSArray *array = @[@"0",@"1",@"2",@"3",@"4",@"5"];
    
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);
    
    //打印done是在dispatch_apply里所有的任務執行完畢之后才會執行
    dispatch_async(queue2, ^(){
        
        //放在dispatch_apply里面的任務的執行順序完全依賴于queue1的隊列串發還是并發
        dispatch_apply([array count], queue1, ^(size_t index) {
            
            NSLog(@"array[%ld]~~~~~~~~~~~~~~%@", index, array[index]);
            
        });
        
        NSLog(@"done");
    });
}

實例七:dispatch_barrier_async

/**
 dispatch_barrier_async的使用
 
 dispatch_barrier_async是在前面的任務執行結束后它才執行,而且它后面的任務等它執行完成之后才會執行
 
 只有當這個隊列為自己創建的并發隊列(DISPATCH_QUEUE_CONCURRENT)時才會有這種效果
 執行結果(一)為:
 dispatch_async2
 dispatch_async1
 dispatch_barrier_async
 dispatch_async3
 
 如果使用dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 根據官方文檔指出,這個時候的dispatch_barrier_async完全等同于dispatch_async
 執行結果(二)為:
 dispatch_barrier_async
 dispatch_async3
 dispatch_async2
 dispatch_async1
 
 在使用DISPATCH_QUEUE_SERIAL串行隊列時,完全就按照FIFO的順序執行了
 執行結果(三)為:
 2015-12-23 16:53:04.574 NSURLDemo[67716:3661098] dispatch_async1
 2015-12-23 16:53:05.577 NSURLDemo[67716:3661098] dispatch_async2
 2015-12-23 16:53:05.578 NSURLDemo[67716:3661098] dispatch_barrier_async
 2015-12-23 16:53:07.085 NSURLDemo[67716:3661098] dispatch_async3
 
 不僅有dispatch_barrier_async方法,還有dispatch_barrier_sync方法,兩個方法的區別是:
 dispatch_barrier_async當他把這個barrier添加到隊列后,當前隊列不用等待block的執行返回
 而dispatch_barrier_sync需要等待block的內容執行完畢之后再繼續下面的執行
 **/
- (void)test7
{
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    //dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:3];
        
        NSLog(@"dispatch_async1~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);
        
    });
    
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:1];
        
        NSLog(@"dispatch_async2~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);

        
    });
    
    dispatch_barrier_async(queue, ^{
        
        [NSThread sleepForTimeInterval:0.5];
        
        NSLog(@"dispatch_barrier_async~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);

        
    });
    
    dispatch_async(queue, ^{
        
        [NSThread sleepForTimeInterval:1];
        
        NSLog(@"dispatch_async3~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);

    });
}

實例八:dispatch_once一次使用函數

/**
 dispatch_once
 dispatch_once這個函數,它可以保證整個應用程序生命周期中某段代碼只被執行一次!
 **/
- (void)test8
{
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        
        // code to be executed once
    });
}

實例九:dispatch_after延遲函數

/**
 dispatch_after
 有時候我們需要等個幾秒鐘然后做個動畫或者給個提示,這時候可以用dispatch_after這個函數:
 **/
-(void)test9
{
    double delayInSeconds = 2.0;
    
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        
        // code to be executed on the main queue after delay
    });
}

實例十:dispatch_set_target_queue轉換隊列

/**
  dispatch_set_target_queue
  通過dispatch_set_target_queue函數可以設置一個dispatch queue的優先級,或者指定一個dispatch source相應的事件處理提交到哪個queue上。
  它會把需要執行的任務對象指定到不同的隊列中去處理,這個任務對象可以是dispatch隊列,也可以是dispatch源。而且這個過程可以是動態的,可以實現隊列的動態調度管理等等。比如說有兩個隊列dispatchA和dispatchB,這時把dispatchA指派到dispatchB:
  dispatch_set_target_queue(dispatchA, dispatchB);
  那么dispatchA上還未運行的block會放到dispatchB上,然后由dispatchB來進行管理運行。
 **/

  dispatch_set_target_queue(serialQ, globalQ);

實例十一:dispatch_group_wait等待

//dispatch_group_wait來等待這些任務完成。若任務已經全部完成或為空,則直接返回,否則等待所有任務完成后返回。

//dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

-(void)test10
{
    
    dispatch_group_t group = dispatch_group_create();
    
    //注意queue為DISPATCH_QUEUE_CONCURRENT 和 DISPATCH_QUEUE_SERIAL時當前的線程
    
    dispatch_queue_t queue = dispatch_queue_create([@"com.queue" UTF8String], DISPATCH_QUEUE_SERIAL);
    
    //每個dispatch_group_enter對應一個dispatch_group_leave完成group內所有的任務則發送通知
    
    //進入group
    
    dispatch_group_enter(group);
    
    dispatch_async(queue, ^{
        
        NSLog(@"task1~~~~~currentThread=%@~~~~~mainThread=%@", [NSThread currentThread], [NSThread mainThread]);
        
        //離開group
        
        dispatch_group_leave(group);
        
    });
    
    
    dispatch_group_enter(group);
    
    dispatch_async(queue, ^{
        
        NSLog(@"task2~~~~~currentThread=%@~~~~~mainThread=%@", [NSThread currentThread], [NSThread mainThread]);
        
        dispatch_group_leave(group);
        
    });
    
    NSLog(@"~~~~~~~~~~~~~~~~~~before group wait");
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"~~~~~~~~~~~~~~~~~~after group wait");
    
    dispatch_group_notify(group, queue, ^{
        
        NSLog(@"~~~~~~~~~~~~~allGroupFinish");
        
    });
}

補充:dispatch_sync(dispatch_get_main_queue(),...)造成死鎖的原因

當這段代碼放在主線程里,也即dispatch_get_main_queue()中,執行到sync時向dispatch_get_main_queue()插入同步thread,sync會等到里面的block執行完成才返回。sync又在主隊列里面,是個串行隊列,sync是后面才加入的,前面一個是主線程,所以sync想執行block必須等待前一個主線程執行完成,而主線程卻在等待sync返回,才能執行后續工作,從而造成死鎖。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容

  • 多線程學習筆記-GCD 我把這篇文章所用到的代碼總結到這里->GCD項目總結下載地址-GCD-wxk可以下載參考 ...
    wxkkkkk閱讀 542評論 0 2
  • 一、簡介在iOS所有實現多線程的方案中,GCD應該是最有魅力的,因為GCD本身是蘋果公司為多核的并行運算提出的解決...
    MYS_iOS_8801閱讀 576評論 0 0
  • 前言 嘿嘿嘿,精品。 概述 全稱是Grand Central Dispatch,可譯為“牛逼的中樞調度器”。純C語...
    Ostkaka丶閱讀 1,108評論 0 12
  • 一、基本概念 線程是用來執行任務的,線程徹底執行完任務A才能執行任務B,為了同時執行兩個任務,產生了多線程 1、進...
    空白Null閱讀 707評論 0 3
  • 你將老去,我將老去,無人幸免。 這個世界上,有些人有多冷漠,有些人就有多溫暖,希望你總是遇到,那些溫暖對你...
    一顆半糖閱讀 235評論 0 5