012-GCD多線程技術(shù)

多線程

線程是進(jìn)程內(nèi)部執(zhí)行任務(wù)的一種途徑,多線程技術(shù)能適當(dāng)提高程序執(zhí)行效率和資源利用率任内,iOS 中的多線程技術(shù)主要有以下幾種

  • GCD
  • NSOperation & NSOperationQueue
  • NSThread
  • Pthreads

多線程的創(chuàng)建是需要資源開銷的维费,同時(shí)維護(hù)和調(diào)度線程也需要開銷官册,程序設(shè)計(jì)和線程間通信也會(huì)因線程數(shù)目增多而變得復(fù)雜挣惰。
iOS 的主線程是在一個(gè)應(yīng)用啟動(dòng)后默認(rèn)開啟的哆窿,主要負(fù)責(zé)顯示农曲、刷新和處理 UI社搅,因此不能把比較耗時(shí)的任務(wù)放在主線程完成,會(huì)帶來 UI 卡頓問題乳规。
同時(shí)多線程也會(huì)帶來線程安全的問題形葬,當(dāng)多個(gè)線程同時(shí)訪問同一個(gè)對(duì)象或是存儲(chǔ)空間時(shí),由于讀寫操作的非原子性或是線程間的協(xié)同不夠暮的,就會(huì)帶來嚴(yán)重的數(shù)據(jù)丟失或錯(cuò)亂問題笙以,因此需要對(duì)資源進(jìn)行同步加鎖操作。

@synchronized(鎖對(duì)象) { // 需要鎖定的代碼  };

當(dāng)然在定義屬性的時(shí)候也可以通過設(shè)置 atomic 特性來為屬性的讀寫方法加鎖冻辩。

GCD

GCD 是 iOS 用來管理多線程的技術(shù)源织,它會(huì)自動(dòng)利用更多 CPU 內(nèi)核,自動(dòng)管理線程生命周期微猖,使得使用線程變得更加輕便簡(jiǎn)潔谈息。

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

GCD 中加入了兩個(gè)重要的概念,任務(wù)和隊(duì)列凛剥。

  • 任務(wù)

在 GCD 中任務(wù)表現(xiàn)為一個(gè)個(gè) block侠仇,包含需要執(zhí)行的代碼,任務(wù)的執(zhí)行分兩種犁珠,同步執(zhí)行和異步執(zhí)行逻炊,同步執(zhí)行會(huì)阻塞當(dāng)前線程,異步執(zhí)行會(huì)創(chuàng)建新線程犁享,不會(huì)阻塞當(dāng)前線程余素。

dispatch_async 方法會(huì)異步執(zhí)行任務(wù),這意味著它不會(huì)阻塞當(dāng)前線程

-(void)func{
    dispatch_async(qQueue, ^{
        NSLog(@"1");
    });
    NSLog(@"2");
}

這里 1 和 2 的打印順序不固定炊昆,因?yàn)?async 執(zhí)行任務(wù)不會(huì)阻塞桨吊,所以當(dāng)前線程會(huì)繼續(xù)向下執(zhí)行威根。

dispatch_async 方法會(huì)同步執(zhí)行任務(wù),意味著當(dāng)前線程一直阻塞到它完成任務(wù)才會(huì)繼續(xù)執(zhí)行

-(void)func{
    dispatch_sync(qQueue, ^{
        NSLog(@"1");
    });
    NSLog(@"2");
}

這里打印順序一定是先 1 后 2 的视乐。

  • 隊(duì)列

隊(duì)列用于存放任務(wù)洛搀,然后由 Run Loop 從中取出任務(wù)分發(fā)給各個(gè)線程。隊(duì)列也分為串行隊(duì)列和并行隊(duì)列佑淀。串行隊(duì)列會(huì)按照 FIFO 順序被執(zhí)行留美,并行隊(duì)列則會(huì)并行執(zhí)行,并行隊(duì)列只能在異步函數(shù)中起作用伸刃。

創(chuàng)建隊(duì)列

  • 主隊(duì)列

主隊(duì)列是一個(gè)特殊的串行隊(duì)列谎砾,放在主隊(duì)列的任務(wù)都會(huì)放到主線程執(zhí)行

dispatch_queue_t queue = dispatch_get_main_queue();
  • 全局并行隊(duì)列

全局并行隊(duì)列是系統(tǒng)提供的并行隊(duì)列,無需手動(dòng)創(chuàng)建捧颅。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

這里第二個(gè) flag 參數(shù)是保留位景图,使用時(shí)要置0,第一個(gè)參數(shù)表示線程優(yōu)先級(jí)隘道,也就是說有4個(gè)可選的全局并行隊(duì)列症歇,優(yōu)先級(jí)分別是

#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
  • 串行隊(duì)列
// 創(chuàng)建串行隊(duì)列(隊(duì)列類型傳遞NULL或者DISPATCH_QUEUE_SERIAL)
dispatch_queue_t queue = dispatch_queue_create("serial_queue", NULL);
  • 并行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);

自定義隊(duì)列的優(yōu)先級(jí)有兩種定義方法

  • dispatch_queue_attr_make_with_qos_class 方法

    dispatch_queue_attr_t queue_attr = dispatch_queue_attr_make_with_qos_class (DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY,-1);
    dispatch_queue_t queue = dispatch_queue_create("queue", queue_attr);
    
  • dispatch_set_target_queue

      dispatch_queue_t queue = dispatch_queue_create("myqueue", NULL);
      dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
      dispatch_set_target_queue(queue, globalQueue);
      dispatch_async(queue, ^{
          NSLog(@"async");
          dispatch_async(dispatch_get_main_queue(), ^{
              NSLog(@"main");
          });
      });
    

這個(gè)方法還可以設(shè)置隊(duì)列層次結(jié)構(gòu)郎笆,當(dāng)我們想讓不同隊(duì)列的任務(wù)同步執(zhí)行時(shí)谭梗,可以創(chuàng)建一個(gè)串行隊(duì)列,將其他隊(duì)列的 target 設(shè)置為這個(gè)隊(duì)列宛蚓,就可以實(shí)現(xiàn)了激捏。

    dispatch_queue_t targetQueue = dispatch_queue_create("myqueue", NULL);
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_set_target_queue(targetQueue, globalQueue);

    dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_async(queue1, ^{
        NSLog(@"queue1 1");
    });
    dispatch_async(targetQueue, ^{
        NSLog(@"async");
    });
    dispatch_async(queue2, ^{
        NSLog(@"queue2 1");
    });
    dispatch_async(queue2, ^{
        NSLog(@"queue2 2");
    });
    dispatch_async(queue2, ^{
        NSLog(@"queue2 3");
    });
    dispatch_async(queue1, ^{
        NSLog(@"queue1 2");
    });
    dispatch_async(queue1, ^{
        sleep(1);
        NSLog(@"queue1 3");
    });

這樣執(zhí)行的結(jié)果如下

queue1 1
queue1 2
queue1 3
async
queue2 1
queue2 2
queue2 3

簡(jiǎn)單來說,設(shè)置了 target 以后凄吏,并不是按照設(shè)置的隊(duì)列順序來執(zhí)行任務(wù)的远舅,而是按照分發(fā)任務(wù)的隊(duì)列順序來執(zhí)行,如果先設(shè)置了 queue1 的任務(wù)痕钢,就會(huì)將 queue1 的任務(wù)執(zhí)行完再執(zhí)行其他隊(duì)列图柏。

分發(fā)任務(wù)

dispatch_async

前面提到過,這個(gè)方法是異步執(zhí)行代碼塊中任務(wù)任连。

dispatch_sync

這個(gè)方法會(huì)同步執(zhí)行任務(wù)蚤吹,執(zhí)行順序可以預(yù)測(cè),但是要注意使用不當(dāng)可能會(huì)造成死鎖随抠。

dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"123");
    });
NSLog(@"456");

在這里裁着,dispatch_sync 由于是在主線程運(yùn)行,因此會(huì)阻塞主線程拱她,一直等到 block 中的代碼執(zhí)行返回后才會(huì)繼續(xù)執(zhí)行二驰,但是 block 又是在主線程執(zhí)行的,因?yàn)橹骶€程被阻塞所以不會(huì)執(zhí)行 block秉沼,于是兩者相互等待桶雀,就發(fā)生了死鎖矿酵。

once

dispatch_once 保證 block 只會(huì)被執(zhí)行一次,一般用于單例模式中初始化 static 的單例對(duì)象背犯。

    static dispatch_once_t onceToken;
    NSLog(@"%ld", onceToken);
    dispatch_once(&onceToken, ^{
        
    });
    NSLog(@"%ld", onceToken);

打印結(jié)果可以看到 onceToken 一開始是 0坏瘩,執(zhí)行完以后會(huì)變成 -1,從而標(biāo)識(shí)這個(gè) block 已被執(zhí)行過漠魏。

apply

dispatch_apply 這個(gè)方法會(huì)循環(huán)執(zhí)行任務(wù)倔矾,指定循環(huán)次數(shù)就會(huì)在 queue 中將 block 循環(huán)執(zhí)行指定次數(shù)。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(5, queue, ^(size_t i) {
        if (i == 3)
        {
            sleep(1);
        }
        NSLog(@"%zu", i);
    });

當(dāng)然至于是串行執(zhí)行還是并行執(zhí)行則要看隊(duì)列的屬性柱锹。

group

group 是一組執(zhí)行的任務(wù)哪自,適用于需要等待一組任務(wù)完成觸發(fā)其他代碼的場(chǎng)景。

  • 創(chuàng)建一個(gè) group 對(duì)象

    dispatch_group_t group = dispatch_group_create();
    
  • 插入到隊(duì)列中

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
    });
    
  • 同步等待所有任務(wù)完成后繼續(xù)執(zhí)行后面的代碼

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    

    這里 timeout 可以設(shè)置為 FOREVER禁熏,也可以設(shè)置為 dispatch_time_t 類型的參數(shù)

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)1 * NSEC_PER_SEC);
      dispatch_group_wait(group, time);
    
  • 異步等待所有任務(wù)完成后執(zhí)行

        dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          NSLog(@"finish");
      });
    
  • enter 和 leave

    也可以使用 dispatch_group_enter 和 dispatch_group_leave 方法實(shí)現(xiàn)將任務(wù)加入到 group 中的操作

        dispatch_group_t group = dispatch_group_create();
      dispatch_group_enter(group);
      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
      dispatch_async(queue, ^{
          NSLog(@"group in");
          sleep(1);
          dispatch_group_leave(group);
      });
      NSLog(@"main");
    

    這里效果與 dispatch_group_wait 相同壤巷。

after

dispatch_after 會(huì)在指定時(shí)間后將任務(wù)加入到指定隊(duì)列中,當(dāng)然不代表會(huì)立刻執(zhí)行此任務(wù)瞧毙。

    dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t) 3 * NSEC_PER_SEC);
    dispatch_after(time, queue, ^{
        NSLog(@"after 3 second");
    });

barrier

dispatch_barrier_async 用于等待前面的任務(wù)執(zhí)行完畢后自己才執(zhí)行胧华,而它后面的任務(wù)需等待它完成之后才執(zhí)行。還有個(gè)串行函數(shù) dispatch_barrier_sync 是會(huì)阻塞當(dāng)前線程等待指定隊(duì)列完成任務(wù)再繼續(xù)執(zhí)行的宙彪。

    dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL);
    
    dispatch_async(queue1, ^{
        NSLog(@"1 1");
    });
    
    dispatch_async(queue1, ^{
        sleep(1);
        NSLog(@"1 2");
    });
    
    dispatch_barrier_sync(queue1, ^{
        NSLog(@"1 3");
    });
    
    dispatch_async(queue2, ^{
        NSLog(@"2 1");
    });

打印結(jié)果是

1 1
1 2
1 3
2 1

如果是執(zhí)行的 dispatch_barrier_async 則 "2 1" 不會(huì)最后才打印矩动。

block

可以看到插入到隊(duì)列的任務(wù)一般就是 block 代碼塊中的代碼,也可以自己定義一個(gè) block 進(jìn)行復(fù)用释漆。

    dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL);
    
    dispatch_block_t block1 = dispatch_block_create(0, ^{
        NSLog(@"block1");
    });
    
    dispatch_block_t block2 = dispatch_block_create(0, ^{
        NSLog(@"block2");
    });
    
    dispatch_async(queue1, block1);
    dispatch_async(queue2, block2);
  • dispatch_block_cancel

    這個(gè)函數(shù)可以取消 block 的執(zhí)行悲没,例如

        dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
      dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL);
      
      dispatch_block_t block1 = dispatch_block_create(0, ^{
          NSLog(@"block1");
      });
      
      dispatch_block_t block2 = dispatch_block_create(0, ^{
          NSLog(@"block2");
      });
      
      dispatch_async(queue1, block1);
      dispatch_async(queue2, block2);
      dispatch_block_cancel(block1);
    

    這樣將只會(huì)打印 "block2"。

  • dispatch_block_wait

    這個(gè)函數(shù)會(huì)阻塞當(dāng)前線程男图,并等待前面的任務(wù)執(zhí)行完畢示姿。

        dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
      
      dispatch_block_t block1 = dispatch_block_create(0, ^{
          sleep(1);
          NSLog(@"block1");
      });
      
      dispatch_async(queue1, block1);
      dispatch_block_wait(block1, DISPATCH_TIME_FOREVER);
      NSLog(@"continue");
    

    最終打印結(jié)果是

    block1
    continue
    
  • dispatch_block_notify

    dispatch_block_notify 不會(huì)阻塞當(dāng)前線程,會(huì)在指定的 block 執(zhí)行結(jié)束后將指定 block 插入到指定的 queue 中逊笆。

        dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
      dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL);
      dispatch_block_t block1 = dispatch_block_create(0, ^{
          sleep(1);
          NSLog(@"block1");
      });
      dispatch_block_t block2 = dispatch_block_create(0, ^{
          NSLog(@"block2");
      });
      dispatch_async(queue1, block1);
      dispatch_block_notify(block1, queue2, block2);
      NSLog(@"continue");
    

    打印結(jié)果為

    continue
    block1
    block2
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末栈戳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子难裆,更是在濱河造成了極大的恐慌子檀,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件差牛,死亡現(xiàn)場(chǎng)離奇詭異命锄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)偏化,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門脐恩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人侦讨,你說我怎么就攤上這事驶冒」斗” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵骗污,是天一觀的道長崇猫。 經(jīng)常有香客問我,道長需忿,這世上最難降的妖魔是什么诅炉? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮屋厘,結(jié)果婚禮上涕烧,老公的妹妹穿的比我還像新娘。我一直安慰自己汗洒,他們只是感情好议纯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著溢谤,像睡著了一般瞻凤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上世杀,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天阀参,我揣著相機(jī)與錄音,去河邊找鬼玫坛。 笑死结笨,一個(gè)胖子當(dāng)著我的面吹牛包晰,可吹牛的內(nèi)容都是我干的湿镀。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼伐憾,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼勉痴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起树肃,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤蒸矛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后胸嘴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雏掠,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年劣像,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乡话。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耳奕,死狀恐怖绑青,靈堂內(nèi)的尸體忽然破棺而出诬像,到底是詐尸還是另有隱情,我是刑警寧澤闸婴,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布坏挠,位于F島的核電站,受9級(jí)特大地震影響邪乍,放射性物質(zhì)發(fā)生泄漏降狠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一庇楞、第九天 我趴在偏房一處隱蔽的房頂上張望喊熟。 院中可真熱鬧,春花似錦姐刁、人聲如沸芥牌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽壁拉。三九已至,卻和暖如春柏靶,著一層夾襖步出監(jiān)牢的瞬間弃理,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工屎蜓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留痘昌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓炬转,卻偏偏與公主長得像辆苔,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扼劈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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