多線程
線程是進(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