目錄
一宾尚、基本概念
- 1.多線程
- 2.串行和并行丙笋, 并發(fā)
- 3.隊(duì)列與任務(wù)
- 4.同步與異步
- 5.線程狀態(tài)
- 6.多線程方案
二、GCD
- 1.GCD簡(jiǎn)介
- 2.GCD的優(yōu)勢(shì)
- 3.GCD任務(wù)和隊(duì)列
- 4.任務(wù)的管理(調(diào)度)
- 5.任務(wù)的執(zhí)行
- 6.同步煌贴,異步御板,并發(fā),串行的組合
- 7.GCD其他函數(shù)
- 7.1延遲執(zhí)行
- 7.2 一次性代碼
- 7.3 隊(duì)列組
- 7.4 快速迭代dispatch_apply函數(shù)
- 7.5.dispatch_barrier_async
- 7.6.補(bǔ)充
三牛郑、NSOperation和NSOperationQueue
- 1.NSOperation
- 1.1NSInvocationOperation
- 1.2.NSBlockOperation
- 1.3.自定義NSOperation
- 1.4.操作之間的關(guān)系 -- 操作依賴
- 1.5.操作的監(jiān)聽(tīng)(KVO)
- 2.NSOperationQueue
- 2.1使用
- 2.2設(shè)置最大并發(fā)數(shù)
- 2.3隊(duì)列的取消怠肋,暫停和恢復(fù)
四、補(bǔ)充
- 1.GCD和NSOperation區(qū)別
- 2.線程間的通信
- 3.線程安全
- 4.熟悉SDWebImage的實(shí)現(xiàn)原理
一淹朋、基本概念
1.多線程
-
什么是多線程笙各?
進(jìn)程是資源分配的最小單位,線程是CPU調(diào)度的最小單位础芍。
多線程:是指從軟件或者硬件上實(shí)現(xiàn)多個(gè)線程的并發(fā)技術(shù)杈抢。試想一下,一個(gè)任務(wù)由十個(gè)子任務(wù)組成÷匦裕現(xiàn)在有兩種方式完成這個(gè)任務(wù) 1.建十個(gè)線程惶楼,把每個(gè)子任務(wù)放在對(duì)應(yīng)的線程中執(zhí)行。 2.把十個(gè)任務(wù)放在一個(gè)線程里诊杆,按順序執(zhí)行歼捐。 效率后者完勝前者 ?
了解了多線程的原理后晨汹,你會(huì)有更深的理解
-
多線程的原理
-
1個(gè)線程中任務(wù)的執(zhí)行是串行的
- 如果要在1個(gè)線程中執(zhí)行多個(gè)任務(wù)豹储,那么只能一個(gè)一個(gè)地按順序執(zhí)行這些任,也就是說(shuō)淘这,在同一時(shí)間內(nèi)剥扣,1個(gè)線程只能執(zhí)行1個(gè)任務(wù)
-
1個(gè)進(jìn)程中可以開(kāi)啟多條線程,每條線程可以并行(同時(shí))執(zhí)行不同的任務(wù)
對(duì)于單核
- 同一時(shí)間铝穷,單核CPU只能處理1條線程朦乏,只有1條線程在工作(執(zhí)行)
- 多線程并發(fā)(同時(shí))執(zhí)行,其實(shí)是CPU快速地在多條線程之間調(diào)度(切換)氧骤,如果CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象
對(duì)于多核
- 多線程并發(fā)(同時(shí))執(zhí)行吃引,就是多條線程同時(shí)執(zhí)行任務(wù)
并不是所有的框架都支持多線程, 必須要有多核的cpu支持才行, 單核cpu即使開(kāi)了多線程運(yùn)行速度也不會(huì)有變化筹陵,過(guò)多的線程會(huì)占用大量的內(nèi)存刽锤,目前主線程和子線程的開(kāi)銷均為512kb
-
多線程的好處:
(1)使用多線程可以把程序中占據(jù)時(shí)間長(zhǎng)的任務(wù)放到后臺(tái)去處理,如圖片朦佩、視屏的下載
(2)發(fā)揮多核處理器的優(yōu)勢(shì)并思,并發(fā)執(zhí)行讓系統(tǒng)運(yùn)行的更快、更流暢语稠,用戶體驗(yàn)更好(多核)多線程的缺點(diǎn):
(1)大量的線程降低代碼的可讀性宋彼;
(2)更多的線程需要更多的內(nèi)存空間
(3)當(dāng)多個(gè)線程對(duì)同一個(gè)資源出現(xiàn)爭(zhēng)奪時(shí)候要注意線程安全的問(wèn)題
2.串行和并行, 并發(fā)
并行指的是一種技術(shù)仙畦,一個(gè)同時(shí)處理多個(gè)任務(wù)的技術(shù)输涕。它描述了一種能夠同時(shí)處理多個(gè)任務(wù)的能力,側(cè)重點(diǎn)在于“運(yùn)行”慨畸。
并行的反義詞就是串行莱坎,表示任務(wù)必須按順序來(lái),一個(gè)一個(gè)執(zhí)行寸士,前一個(gè)執(zhí)行完了才能執(zhí)行后一個(gè)檐什。
并發(fā)指的是一種現(xiàn)象,一種經(jīng)常出現(xiàn)薪缆,無(wú)可避免的現(xiàn)象沮协。它描述的是“多個(gè)任務(wù)同時(shí)發(fā)生躏啰,需要被處理”這一現(xiàn)象。它的側(cè)重點(diǎn)在于“發(fā)生”瓮具。
比如在一個(gè)景點(diǎn),一個(gè)游客買票對(duì)應(yīng)售票處來(lái)說(shuō)凡蜻,當(dāng)做一個(gè)任務(wù)搭综,而很多游客需要買票,同時(shí)發(fā)生划栓,需要被處理兑巾,這種現(xiàn)象稱為并發(fā)
景點(diǎn)開(kāi)放了多個(gè)買票窗口,同一時(shí)間內(nèi)能服務(wù)多個(gè)游客忠荞。這種情況可以理解為并行
-
進(jìn)入景點(diǎn)的控制門蒋歌,一次只能一個(gè)人通過(guò),這叫串行委煤;
我們經(jīng)常掛在嘴邊的“多線程”堂油,正是采用了并行技術(shù),從而提高了執(zhí)行效率碧绞。因?yàn)橛卸鄠€(gè)線程府框,所以計(jì)算機(jī)的多個(gè)CPU可以同時(shí)工作,同時(shí)處理不同線程內(nèi)的指令讥邻。
3.隊(duì)列與任務(wù)
隊(duì)列只是負(fù)責(zé)任務(wù)的調(diào)度迫靖,而不負(fù)責(zé)任務(wù)的執(zhí)行院峡, 任務(wù)是在線程中執(zhí)行的
隊(duì)列的特點(diǎn):先進(jìn)先出,排在前面的任務(wù)最先調(diào)度
串行隊(duì)列:任務(wù)按照順序被調(diào)度系宜,前一個(gè)任務(wù)不執(zhí)行完畢照激,隊(duì)列不會(huì)調(diào)度
并行隊(duì)列:只要有空閑的線程,隊(duì)列就會(huì)調(diào)度當(dāng)前任務(wù)盹牧,交給線程去執(zhí)行俩垃,不需要考慮前面是都有任務(wù)在執(zhí)行,只要有線程可以利用汰寓,隊(duì)列就會(huì)調(diào)度任務(wù)口柳。
不管是串行隊(duì)列(SerialQueue)還是并行隊(duì)列(ConcurrencyQueue),都是FIFO隊(duì)列踩寇。也就意味著啄清,任務(wù)一定是一個(gè)一個(gè)地,按照先進(jìn)先出的順序來(lái)調(diào)度俺孙。
4.同步與異步
同步
(sync)
和 異步(async)
的主要區(qū)別在于會(huì)不會(huì)阻塞當(dāng)前線程辣卒,直到 Block 中的任務(wù)執(zhí)行完畢!
如果是 同步(sync)
操作睛榄,它會(huì)阻塞當(dāng)前線程并等待 Block 中的任務(wù)執(zhí)行完畢荣茫,然后當(dāng)前線程才會(huì)繼續(xù)往下運(yùn)行。
如果是 異步(async)
操作场靴,當(dāng)前線程會(huì)直接往下執(zhí)行啡莉,它不會(huì)阻塞當(dāng)前線程。
同步和異步關(guān)注的是消息通信機(jī)制 (synchronous communication/ asynchronous communication)
所謂同步旨剥,就是在發(fā)出一個(gè)調(diào)用時(shí)咧欣,在沒(méi)有得到結(jié)果之前,該調(diào)用就不返回轨帜。但是一旦調(diào)用返回魄咕,就得到返回值了。
換句話說(shuō)蚌父,就是由調(diào)用者主動(dòng)等待這個(gè)調(diào)用的結(jié)果哮兰。
而異步則是相反,調(diào)用在發(fā)出之后苟弛,這個(gè)調(diào)用就直接返回了喝滞,所以沒(méi)有返回結(jié)果。換句話說(shuō)膏秫,當(dāng)一個(gè)異步過(guò)程調(diào)用發(fā)出后右遭,調(diào)用者不會(huì)立刻得到結(jié)果。而是在調(diào)用發(fā)出后,被調(diào)用者通過(guò)狀態(tài)狸演、通知來(lái)通知調(diào)用者言蛇,或通過(guò)回調(diào)函數(shù)處理這個(gè)調(diào)用。
關(guān)于同步異步宵距、串行并行和線程的關(guān)系,如下表格所示
同步 異步 主隊(duì)列 在主線程中執(zhí)行 在主線程中執(zhí)行 串行隊(duì)列 在當(dāng)前線程中執(zhí)行 新建線程執(zhí)行 并發(fā)隊(duì)列 在當(dāng)前線程中執(zhí)行 新建線程執(zhí)行
5.線程狀態(tài)
一般來(lái)說(shuō)吨拗,線程有五個(gè)狀態(tài)
- 新建狀態(tài):線程通過(guò)各種方式在被創(chuàng)建之初满哪,還沒(méi)有調(diào)用開(kāi)始執(zhí)行方法,這個(gè)時(shí)候的線程就是新建狀態(tài)劝篷;
self.thread=[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
- 就緒狀態(tài):在新建線程被創(chuàng)建之后調(diào)用了開(kāi)始執(zhí)行方法
[thread start]
哨鸭,但是CPU并不是真正的同時(shí)執(zhí)行多個(gè)任務(wù),所以要等待CPU調(diào)用娇妓,這個(gè)時(shí)候線程處于就緒狀態(tài)像鸡。處于就緒狀態(tài)的線程并不一定立即執(zhí)行線程里的代碼,線程還必須同其他線程競(jìng)爭(zhēng)CPU時(shí)間哈恰,只有獲得CPU時(shí)間才可以運(yùn)行線程只估。
- 運(yùn)行狀態(tài):線程獲得CPU時(shí)間,被CPU調(diào)用之后就進(jìn)入運(yùn)行狀態(tài)
- CPU 負(fù)責(zé)調(diào)度可調(diào)度線程池中線程的執(zhí)行
- 線程執(zhí)行完成之前(死亡之前)着绷,狀態(tài)可能會(huì)在就緒和運(yùn)行之間來(lái)回切換
- 就緒和運(yùn)行之間的狀態(tài)變化由 CPU 負(fù)責(zé)蛔钙,程序員不能干預(yù)
- 阻塞狀態(tài):所謂阻塞狀態(tài)是正在運(yùn)行的線程沒(méi)有運(yùn)行結(jié)束,暫時(shí)讓出CPU荠医,這時(shí)其他處于就緒狀態(tài)的線程就可以獲得CPU時(shí)間吁脱,進(jìn)入運(yùn)行狀態(tài)。
- 線程通過(guò)調(diào)用sleep方法進(jìn)入睡眠狀態(tài)
[NSThread sleepForTimeInterval:2.0];
- 線程調(diào)用一個(gè)在I/O上被阻塞的操作彬向,即該操作在輸入輸出操作完成之前不會(huì)返回到它的調(diào)用者
- 線程試圖得到一個(gè)鎖兼贡,而該鎖正被其他線程持有;
- 線程在等待某個(gè)觸發(fā)條件
- 死亡狀態(tài):當(dāng)線程的任務(wù)結(jié)束娃胆,發(fā)生異常遍希,或者是強(qiáng)制退出這三種情況會(huì)導(dǎo)致線程的死亡。線程死亡后缕棵,線程對(duì)象從內(nèi)存中移除孵班。一旦線程進(jìn)入死亡狀態(tài),再次嘗試重新開(kāi)啟線程招驴,則程序會(huì)掛篙程。
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 1.創(chuàng)建線程--->新建狀態(tài)
self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(therad) object:nil];
// 設(shè)置線程名稱
self.thread.name = @"線程A";
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 2.開(kāi)啟線程---->就緒和運(yùn)行狀態(tài)
[self.thread start];
}
-(void)therad {
NSThread *current=[NSThread currentThread];
NSLog(@"therad---打印線程---%@",self.thread.name);
NSLog(@"therad---線程開(kāi)始---%@",current.name);
//3.設(shè)置線程阻塞1,阻塞2秒 --->阻塞狀態(tài)
NSLog(@"接下來(lái)别厘,線程阻塞2秒");
[NSThread sleepForTimeInterval:2.0];
//第二種設(shè)置線程阻塞2虱饿,以當(dāng)前時(shí)間為基準(zhǔn)阻塞4秒
NSLog(@"接下來(lái),線程阻塞4秒");
NSDate *date=[NSDate dateWithTimeIntervalSinceNow:4.0];
[NSThread sleepUntilDate:date];
for (int i=0; i<10; i++) {
NSLog(@"線程--%d--%@",i,current.name);
// 結(jié)束線程
[NSThread exit];
}
// 4. 任務(wù)結(jié)束 --- >死亡狀態(tài)
NSLog(@"test---線程結(jié)束---%@",current.name);
}
@ end
6.多線程方案
|多線程實(shí)現(xiàn)方案| 特點(diǎn) | 語(yǔ)言|頻率|線程生命周期|
|:------|:----:| :-:|
|pthread | 1、一套通用的多線程API 2氮发、適用于Unix\Linux\Windows等系統(tǒng) 3渴肉、跨平臺(tái)\可移植 4、使用難度大 | c語(yǔ)言 |幾乎不用|由程序員進(jìn)行管理|
|NSThread | 1爽冕、使用更加面向?qū)ο?2仇祭、簡(jiǎn)單易用,可直接操作線程對(duì)象 | OC語(yǔ)言 |偶爾使用|由程序員進(jìn)行管理|
|GCD | 1颈畸、旨在替代NSThread等線程技術(shù) 2乌奇、充分利用設(shè)備的多核(自動(dòng)) | c語(yǔ)言 |經(jīng)常使用|自動(dòng)管理|
|NSOperation | 1、基于GCD 2眯娱、比GCD多了一些更簡(jiǎn)單實(shí)用的功能礁苗,使用更加面向?qū)ο?| OC語(yǔ)言 |經(jīng)常使用|自動(dòng)管理|
二、GCD
1. GCD簡(jiǎn)介
全稱是Grand Central Dispatch徙缴,可譯為“牛逼的中樞調(diào)度器”
純C語(yǔ)言试伙,提供了非常多強(qiáng)大的函數(shù)
2.GCD的優(yōu)勢(shì)
GCD是蘋果公司為多核的并行運(yùn)算提出的解決方案
GCD會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核)
GCD會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程于样、調(diào)度任務(wù)疏叨、銷毀線程)
程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼
3.任務(wù)和隊(duì)列
GCD有兩大重要概念百宇,分別是隊(duì)列和任務(wù)
隊(duì)列:這里的隊(duì)列指任務(wù)隊(duì)列考廉,即用來(lái)存放任務(wù)的隊(duì)列。隊(duì)列是一種特殊的線性表携御,采用FIFO(先進(jìn)先出)的原則昌粤,即新任務(wù)總是被插入到隊(duì)列的末尾,而讀取任務(wù)的時(shí)候總是從隊(duì)列的頭部開(kāi)始讀取啄刹。每讀取一個(gè)任務(wù)涮坐,則從隊(duì)列中釋放一個(gè)任務(wù)
任務(wù):就是執(zhí)行操作的意思,換句話說(shuō)就是你在線程中執(zhí)行的那段代碼誓军。在GCD中是放在block中的袱讹。
4.任務(wù)的管理(調(diào)度)
在GCD中有兩種隊(duì)列:串行隊(duì)列和并發(fā)隊(duì)列,負(fù)責(zé)任務(wù)的管理
注意:隊(duì)列只負(fù)責(zé)任務(wù)的調(diào)度昵时,不負(fù)責(zé)任務(wù)的執(zhí)行-----這是理解不同函數(shù)捷雕,不同隊(duì)列,任務(wù)是否在新的線程執(zhí)行的關(guān)鍵
-
并發(fā)隊(duì)列(Concurrent Dispatch Queue):只要有空閑的線程壹甥,隊(duì)列就會(huì)調(diào)度當(dāng)前任務(wù)救巷,交給線程去執(zhí)行,不需要考慮前面是都有任務(wù)在執(zhí)行句柠,只要有線程可以利用浦译,隊(duì)列就會(huì)調(diào)度任務(wù)棒假。
// 并發(fā)隊(duì)列的創(chuàng)建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT); // 使用全局并發(fā)隊(duì)列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效,在同步函數(shù)下失去了任務(wù)調(diào)度的并發(fā)能力
-
串行隊(duì)列(Serial Dispatch Queue):任務(wù)按照順序被調(diào)度精盅,前一個(gè)任務(wù)不執(zhí)行完畢帽哑,隊(duì)列不會(huì)調(diào)度,這樣任務(wù)叹俏,都是一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后妻枕,再執(zhí)行下一個(gè)任務(wù))
// 串行隊(duì)列的創(chuàng)建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL); // 使用主隊(duì)列(跟主線程相關(guān)聯(lián)的隊(duì)列) // 主隊(duì)列是GCD自帶的一種特殊的串行隊(duì)列,放在主隊(duì)列中的任務(wù),都會(huì)放到主線程中執(zhí)行 dispatch_queue_t queue = dispatch_get_main_queue();
5.任務(wù)的執(zhí)行
- (1)用同步的方式執(zhí)行任務(wù) dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
參數(shù)說(shuō)明:
queue:隊(duì)列
block:任務(wù)
- (2)用異步的方式執(zhí)行任務(wù) dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
任務(wù)的執(zhí)行方式是由函數(shù)決定的(async和sync)她肯,任務(wù)是封裝在block里佳头。
// 同步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_sync(queue, ^{
NSLog(@"%@",[NSThread currentThread]); // 這里放任務(wù)代碼
});
// 異步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]); // 這里放任務(wù)代碼
});
6.同步,異步晴氨,并發(fā),串行的組合
sync和async:
同步函數(shù)和異步函數(shù):負(fù)責(zé)任務(wù)的執(zhí)行碉输,決定了任務(wù)的執(zhí)行方式籽前。
同步函數(shù)(sync):只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開(kāi)啟新線程的能力
異步執(zhí)行(async):可以在新的線程中執(zhí)行任務(wù)敷钾,具備開(kāi)啟新線程的能力
serial和concurrent:
串行隊(duì)列和并行隊(duì)列:只負(fù)責(zé)任務(wù)的調(diào)度枝哄,不負(fù)責(zé)任務(wù)的執(zhí)行
串行隊(duì)列(Serial Dispatch Queue):任務(wù)按照順序被調(diào)度,前一個(gè)任務(wù)不執(zhí)行完畢阻荒,隊(duì)列不會(huì)調(diào)度挠锥,這樣任務(wù),都是一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后侨赡,再執(zhí)行下一個(gè)任務(wù))
并發(fā)隊(duì)列(Concurrent Dispatch Queue):只要有空閑的線程蓖租,隊(duì)列就會(huì)調(diào)度當(dāng)前任務(wù),交給線程去執(zhí)行羊壹,不需要考慮前面是都有任務(wù)在執(zhí)行蓖宦,只要有線程可以利用,隊(duì)列就會(huì)調(diào)度任務(wù)油猫。
各種組合的效果
并發(fā)隊(duì)列 | 創(chuàng)建串行隊(duì)列 | 主隊(duì)列 | |
---|---|---|---|
同步(sync) | 1稠茂、沒(méi)有開(kāi)啟線程 2、串行執(zhí)行任務(wù) | 1情妖、沒(méi)有開(kāi)啟線程 2睬关、串行執(zhí)行任務(wù) | 1、沒(méi)有開(kāi)啟線程 2毡证、串行執(zhí)行任務(wù) |
異步(async) | 1电爹、有開(kāi)啟線程 2、并發(fā)執(zhí)行任務(wù) | 1情竹、有開(kāi)啟線程 2藐不、串行執(zhí)行任務(wù) | 1匀哄、沒(méi)有開(kāi)啟線程 2、串行執(zhí)行任務(wù) |
代碼示范:
- (1)用異步函數(shù)往并發(fā)隊(duì)列中添加任務(wù)
/**
并發(fā)隊(duì)列+異步函數(shù)
*/
- (void)concurrentQueueInAsyn {
//1.獲得全局的并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.添加任務(wù)到隊(duì)列中雏蛮,就可以執(zhí)行任務(wù)
//異步函數(shù):具備開(kāi)啟新線程的能力
dispatch_async(queue, ^{
NSLog(@"下載圖片1----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下載圖片2----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下載圖片3----%@",[NSThread currentThread]);
});
//打印主線程
NSLog(@"主線程----%@",[NSThread mainThread]);
}
打印結(jié)果:同時(shí)開(kāi)啟三個(gè)子線程
2017-04-17 16:30:45.518 GCD任務(wù)的執(zhí)行[1343:148362] 主線程----<NSThread: 0x6100000696c0>{number = 1, name = main}
2017-04-17 16:30:45.518 GCD任務(wù)的執(zhí)行[1343:148619] 下載圖片2----<NSThread: 0x608000070200>{number = 4, name = (null)}
2017-04-17 16:30:45.518 GCD任務(wù)的執(zhí)行[1343:148618] 下載圖片1----<NSThread: 0x60800006cb40>{number = 3, name = (null)}
2017-04-17 16:30:45.518 GCD任務(wù)的執(zhí)行[1343:148650] 下載圖片3----<NSThread: 0x61000006e000>{number = 5, name = (null)}
- (2)用異步函數(shù)往串行隊(duì)列中添加任務(wù)
/**
串行隊(duì)列+異步函數(shù)
*/
- (void)serialQueueInAsyn {
//打印主線程
NSLog(@"主線程----%@",[NSThread mainThread]);
//1.創(chuàng)建串行隊(duì)列
dispatch_queue_t queue= dispatch_queue_create("11", NULL);
//第一個(gè)參數(shù)為串行隊(duì)列的名稱涎嚼,是c語(yǔ)言的字符串
//第二個(gè)參數(shù)為隊(duì)列的屬性,一般來(lái)說(shuō)串行隊(duì)列不需要賦值任何屬性挑秉,所以通常傳空值(NULL)
//2.添加任務(wù)到隊(duì)列中執(zhí)行
dispatch_async(queue, ^{
NSLog(@"下載圖片1----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下載圖片2----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"下載圖片3----%@",[NSThread currentThread]);
});
//3.釋放資源
//dispatch_release(queue);
}
打印結(jié)果:會(huì)開(kāi)啟線程法梯,但是只開(kāi)啟一個(gè)線程
2017-04-17 16:41:02.665 GCD任務(wù)的執(zhí)行[1359:153689] 主線程----<NSThread: 0x610000069a80>{number = 1, name = main}
2017-04-17 16:41:02.665 GCD任務(wù)的執(zhí)行[1359:153728] 下載圖片1----<NSThread: 0x610000072ec0>{number = 3, name = (null)}
2017-04-17 16:41:02.705 GCD任務(wù)的執(zhí)行[1359:153728] 下載圖片2----<NSThread: 0x610000072ec0>{number = 3, name = (null)}
2017-04-17 16:41:02.705 GCD任務(wù)的執(zhí)行[1359:153728] 下載圖片3----<NSThread: 0x610000072ec0>{number = 3, name = (null)}
- (3)用同步函數(shù)往并發(fā)隊(duì)列中添加任務(wù)
/**
異步隊(duì)列+同步函數(shù)
*/
- (void)concurrentQueueInSyn {
//打印主線程
NSLog(@"主線程----%@",[NSThread mainThread]);
//1.創(chuàng)建并行隊(duì)列
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.添加任務(wù)到隊(duì)列中執(zhí)行
dispatch_sync(queue, ^{
NSLog(@"下載圖片1----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"下載圖片2----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"下載圖片3----%@",[NSThread currentThread]);
});
}
打印結(jié)果:不會(huì)開(kāi)啟新的線程,并發(fā)隊(duì)列失去了并發(fā)的功能
2017-04-17 16:47:17.868 GCD任務(wù)的執(zhí)行[1374:157934] 主線程----<NSThread: 0x618000070e80>{number = 1, name = main}
2017-04-17 16:47:17.868 GCD任務(wù)的執(zhí)行[1374:157934] 下載圖片1----<NSThread: 0x618000070e80>{number = 1, name = main}
2017-04-17 16:47:17.868 GCD任務(wù)的執(zhí)行[1374:157934] 下載圖片2----<NSThread: 0x618000070e80>{number = 1, name = main}
2017-04-17 16:47:17.868 GCD任務(wù)的執(zhí)行[1374:157934] 下載圖片3----<NSThread: 0x618000070e80>{number = 1, name = main}
- (4)用同步函數(shù)往串行隊(duì)列中添加任務(wù)
/**
串行隊(duì)列+同步函數(shù)
*/
- (void)serialQueueInSyn {
NSLog(@"用同步函數(shù)往串行隊(duì)列中添加任務(wù)");
//打印主線程
NSLog(@"主線程----%@",[NSThread mainThread]);
//創(chuàng)建串行隊(duì)列
dispatch_queue_t queue= dispatch_queue_create("11", NULL);
//2.添加任務(wù)到隊(duì)列中執(zhí)行
dispatch_sync(queue, ^{
NSLog(@"下載圖片1----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"下載圖片2----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"下載圖片3----%@",[NSThread currentThread]);
});
}
打印結(jié)果:不會(huì)開(kāi)啟新的線程
2017-04-17 16:50:19.439 GCD任務(wù)的執(zhí)行[1388:160753] 主線程----<NSThread: 0x610000065600>{number = 1, name = main}
2017-04-17 16:50:19.483 GCD任務(wù)的執(zhí)行[1388:160753] 下載圖片1----<NSThread: 0x610000065600>{number = 1, name = main}
2017-04-17 16:50:19.483 GCD任務(wù)的執(zhí)行[1388:160753] 下載圖片2----<NSThread: 0x610000065600>{number = 1, name = main}
2017-04-17 16:50:19.483 GCD任務(wù)的執(zhí)行[1388:160753] 下載圖片3----<NSThread: 0x610000065600>{number = 1, name = main}
7.GCD其他函數(shù)
7.1 延遲執(zhí)行
在3s后犀概,可能不僅限于3s立哑,執(zhí)行處理,因?yàn)镸ain Dispatch Queue 在主線程的Runloop中執(zhí)行姻灶,所以在比如每隔1/60秒執(zhí)行的Runloop中铛绰,Block最快在3s后執(zhí)行,最慢在3s+1/60s后執(zhí)行产喉,并且在Main Dispatch Queue有大量處理追加或者主線程的處理本身有延遲時(shí)捂掰,這個(gè)時(shí)間會(huì)更長(zhǎng),所以有嚴(yán)格時(shí)間的要求下使用時(shí)會(huì)出現(xiàn)問(wèn)題
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// to do
});
7.2 一次性代碼
使用dispatch_once函數(shù)能保證某段代碼在程序運(yùn)行過(guò)程中只被執(zhí)行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)
});
整個(gè)程序運(yùn)行過(guò)程中曾沈,只會(huì)執(zhí)行一次这嚣。
7.3 隊(duì)列組
有這么1種需求:
首先:分別異步執(zhí)行2個(gè)耗時(shí)的操作
其次:等2個(gè)異步操作都執(zhí)行完畢后,再回到主線程執(zhí)行操作
如果想要快速高效地實(shí)現(xiàn)上述需求塞俱,可以考慮用隊(duì)列組
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執(zhí)行1個(gè)耗時(shí)的異步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執(zhí)行1個(gè)耗時(shí)的異步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步操作都執(zhí)行完畢后姐帚,回到主線程...
});
** 需求:從網(wǎng)絡(luò)上下載兩張圖片,把兩張圖片合并成一張最終顯示在view上障涯。**
- 1.沒(méi)有使用group的方法
/**
沒(méi)有使用group的方法
*/
- (void)nonGroup {
dispatch_async(global_quque, ^{
//下載圖片1
UIImage *image1= [self imageWithUrl:self.imageString1];
NSLog(@"圖片1下載完成---%@",[NSThread currentThread]);
//下載圖片2
UIImage *image2= [self imageWithUrl:self.imageString2];
NSLog(@"圖片2下載完成---%@",[NSThread currentThread]);
//回到主線程顯示圖片
dispatch_async(main_queue, ^{
NSLog(@"顯示圖片---%@",[NSThread currentThread]);
self.imageView1.image=image1;
self.imageView2.image=image2;
//合并兩張圖片
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);
[image1 drawInRect:CGRectMake(0, 0, 100, 100)];
[image2 drawInRect:CGRectMake(100, 0, 100, 100)];
self.imageView3.image=UIGraphicsGetImageFromCurrentImageContext();
//關(guān)閉上下文
UIGraphicsEndImageContext();
NSLog(@"圖片合并完成---%@",[NSThread currentThread]);
});
});
}
//封裝一個(gè)方法罐旗,傳入一個(gè)url參數(shù),返回一張網(wǎng)絡(luò)上下載的圖片
- (UIImage *)imageWithUrl:(NSString *)urlStr {
NSURL *url=[NSURL URLWithString:urlStr];
NSData *data=[NSData dataWithContentsOfURL:url];
UIImage *image=[UIImage imageWithData:data];
return image;
}
打印結(jié)果:這種方式的效率不高像樊,需要等到圖片1.圖片2都下載完成后才行尤莺。
2017-04-17 18:14:05.716 dispatch_group_asyn[1573:207552] 圖片1下載完成---<NSThread: 0x61800006b140>{number = 5, name = (null)}
2017-04-17 18:14:05.795 dispatch_group_asyn[1573:207552] 圖片2下載完成---<NSThread: 0x61800006b140>{number = 5, name = (null)}
2017-04-17 18:14:05.795 dispatch_group_asyn[1573:206907] 顯示圖片---<NSThread: 0x610000064ac0>{number = 1, name = main}
2017-04-17 18:14:05.802 dispatch_group_asyn[1573:206907] 圖片合并完成---<NSThread: 0x610000064ac0>{number = 1, name = main}
- 2.使用group的方法
- (void)group {
//1.創(chuàng)建一個(gè)隊(duì)列組
dispatch_group_t group = dispatch_group_create();
//2.開(kāi)啟一個(gè)任務(wù)下載圖片1
__block UIImage *image1=nil;
dispatch_group_async(group, global_quque, ^{
image1= [self imageWithUrl:@"http://d.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=2b9a12172df5e0fefa1581533d095fcd/cefc1e178a82b9019115de3d738da9773912ef00.jpg"];
NSLog(@"圖片1下載完成---%@",[NSThread currentThread]);
});
//3.開(kāi)啟一個(gè)任務(wù)下載圖片2
__block UIImage *image2=nil;
dispatch_group_async(group, global_quque, ^{
image2= [self imageWithUrl:@"http://h.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=f47fd63ca41ea8d39e2f7c56f6635b2b/1e30e924b899a9018b8d3ab11f950a7b0308f5f9.jpg"];
NSLog(@"圖片2下載完成---%@",[NSThread currentThread]);
});
//同時(shí)執(zhí)行下載圖片1\下載圖片2操作
//4.等group中的所有任務(wù)都執(zhí)行完畢, 再回到主線程執(zhí)行其他操作
dispatch_group_notify(group,main_queue, ^{
NSLog(@"顯示圖片---%@",[NSThread currentThread]);
self.imageView1.image=image1;
self.imageView2.image=image2;
//合并兩張圖片
//注意最后一個(gè)參數(shù)是浮點(diǎn)數(shù)(0.0),不要寫成0生棍。
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);
[image1 drawInRect:CGRectMake(0, 0, 100, 100)];
[image2 drawInRect:CGRectMake(100, 0, 100, 100)];
self.imageView3.image=UIGraphicsGetImageFromCurrentImageContext();
//關(guān)閉上下文
UIGraphicsEndImageContext();
NSLog(@"圖片合并完成---%@",[NSThread currentThread]);
});
}
打印結(jié)果:同時(shí)開(kāi)啟了兩個(gè)子線程颤霎,分別下載圖片
2017-04-17 18:18:07.222 dispatch_group_asyn[1586:210326] 圖片2下載完成---<NSThread: 0x610000077280>{number = 4, name = (null)}
2017-04-17 18:18:07.271 dispatch_group_asyn[1586:210307] 圖片1下載完成---<NSThread: 0x610000077e80>{number = 5, name = (null)}
2017-04-17 18:18:07.271 dispatch_group_asyn[1586:210270] 顯示圖片---<NSThread: 0x618000073240>{number = 1, name = main}
2017-04-17 18:18:07.278 dispatch_group_asyn[1586:210270] 圖片合并完成---<NSThread: 0x618000073240>{number = 1, name = main}
7.4 快速迭代dispatch_apply函數(shù)
- 普通迭代
/**
* 普通迭代
*/
- (void)commonIteration {
for (int i = 0; i < 9; i++) {
NSLog(@"%zd----%@", i, [NSThread currentThread]);
}
}
打印結(jié)果:速度慢
2017-04-17 23:10:26.475 dispatch_apply函數(shù)[575:8754] 0----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.475 dispatch_apply函數(shù)[575:8754] 1----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.475 dispatch_apply函數(shù)[575:8754] 2----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.476 dispatch_apply函數(shù)[575:8754] 3----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.476 dispatch_apply函數(shù)[575:8754] 4----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.476 dispatch_apply函數(shù)[575:8754] 5----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.476 dispatch_apply函數(shù)[575:8754] 6----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.476 dispatch_apply函數(shù)[575:8754] 7----<NSThread: 0x608000079b40>{number = 1, name = main}
2017-04-17 23:10:26.477 dispatch_apply函數(shù)[575:8754] 8----<NSThread: 0x608000079b40>{number = 1, name = main}
- 多線程快速迭代
/**
* 快速迭代(應(yīng)用:拷貝文件)
*/
- (void)quickIteration {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(9, queue, ^(size_t index) {
NSLog(@"%zd----%@", index, [NSThread currentThread]);
});
}
打印結(jié)果:創(chuàng)建多條線程,進(jìn)行迭代涂滴,速度快
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11278] 0----<NSThread: 0x600000261240>{number = 1, name = main}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11318] 3----<NSThread: 0x608000267040>{number = 5, name = (null)}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11335] 1----<NSThread: 0x600000269f40>{number = 3, name = (null)}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11315] 2----<NSThread: 0x6080002668c0>{number = 4, name = (null)}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11278] 4----<NSThread: 0x600000261240>{number = 1, name = main}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11318] 5----<NSThread: 0x608000267040>{number = 5, name = (null)}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11335] 6----<NSThread: 0x600000269f40>{number = 3, name = (null)}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11315] 7----<NSThread: 0x6080002668c0>{number = 4, name = (null)}
2017-04-17 23:12:39.705 dispatch_apply函數(shù)[605:11278] 8----<NSThread: 0x600000261240>{number = 1, name = main}
7.5.dispatch_barrier_async
在訪問(wèn)數(shù)據(jù)庫(kù)或文件時(shí)友酱,使用Serial Dispatch Queue 可避免數(shù)據(jù)競(jìng)爭(zhēng)的問(wèn)題。
寫入處理確實(shí)不可與其他的寫入處理以及包含讀取處理的其他某些處理并行執(zhí)行柔纵。但是如果讀取處理只是讀取處理并行執(zhí)行缔杉,那么多個(gè)并行執(zhí)行就不會(huì)發(fā)生問(wèn)題。
也就是說(shuō)搁料,為了高效率的進(jìn)行訪問(wèn)或详,讀取處理追加到Concurrent Dispatch Queue中系羞,寫入處理在任何一個(gè)讀取處理沒(méi)有執(zhí)行的狀態(tài)下,追加到Serial Dispatch Queue中即可(在寫入處理之前霸琴,讀取處理不可執(zhí)行)
/**
并發(fā)隊(duì)列+異步函數(shù) 讀1和讀2之間寫入數(shù)據(jù)
*/
- (void)concurrentQueueInAsync {
dispatch_queue_t queue = dispatch_queue_create("JJ", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"block for reading 0");
});
dispatch_async(queue, ^{
NSLog(@"block for reading 1");
});
// 在1和2之間執(zhí)行寫入處理,那么根據(jù)Concurrent Dispatch Queue的性質(zhì)椒振,就有可能在追加到寫入處理前面的處理中讀取到與期待不符的數(shù)據(jù)。
dispatch_async(queue, ^{
NSLog(@"block for writing");
});
dispatch_async(queue, ^{
NSLog(@"block for reading 2");
});
dispatch_async(queue, ^{
NSLog(@"block for reading 3");
});
}
因此我們要使用dispatch_barrier_async函數(shù)梧乘。dispatch_barrier_async函數(shù)會(huì)等待追加到Concurrent Dispatch Queue上的并行執(zhí)行的處理全部結(jié)束之后澎迎,再將指定的處理追加到該Concurrent Dispatch Queue中,然后在由dispatch_barrier_async函數(shù)追加的處理執(zhí)行完畢后选调,Concurrent Dispatch Queue才恢復(fù)為一般的動(dòng)作夹供,追加到該Concurrent Dispatch Queue的處理又開(kāi)始并行執(zhí)行
/**
dispatch_barrier_async函數(shù)控制讀寫操作 讀1和讀2之間寫入數(shù)據(jù)
*/
- (void)dispatch_barrier_async {
dispatch_queue_t queue = dispatch_queue_create("JJ", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"block for reading 0");
});
dispatch_async(queue, ^{
NSLog(@"block for reading 1");
});
// 將這個(gè)block之前的任務(wù)攔著,等執(zhí)行完后仁堪,執(zhí)行這個(gè)block哮洽,然后再并行執(zhí)行其他任務(wù)。
dispatch_barrier_async(queue, ^{
NSLog(@"block for writing");
});
dispatch_async(queue, ^{
NSLog(@"block for reading 2");
});
dispatch_async(queue, ^{
NSLog(@"block for reading 3");
});
}
使用Concurrent Dispatch Queue和dispatch_barrier_async函數(shù)可以實(shí)現(xiàn)高效率的數(shù)據(jù)庫(kù)訪問(wèn)和文件訪問(wèn)弦聂。
demo地址
7.6.補(bǔ)充
-
dispatch_sync函數(shù)
將指定的block任務(wù)同步追加到指定的Dispatch Queue中袁铐,在追加Block結(jié)束之前,dispatch_sync函數(shù)會(huì)一直等待横浑,產(chǎn)生死鎖問(wèn)題
在主線程中執(zhí)行以下代碼會(huì)死鎖。
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"block middle");
});
a.dispatch_sync()函數(shù)在主線程中調(diào)用屉更;
b.調(diào)用dispatch_sync()函數(shù)會(huì)立即阻塞調(diào)用時(shí)該函數(shù)所在的線程,即主線程等待dispatch_sync()函數(shù)返回
c.dispatch_sync()函數(shù)追加任務(wù)(即Block代碼塊)到主隊(duì)列dispatch_get_main_queue()中
d.主隊(duì)列是一種特殊的串行隊(duì)列,主隊(duì)列的任務(wù)在主線程中執(zhí)行,但此時(shí)主線程被阻塞,無(wú)法執(zhí)行Block代碼塊,導(dǎo)致dispatch_sync()函數(shù)無(wú)法返回,一直等待Block被主線程執(zhí)行,最終導(dǎo)致死鎖
也就是說(shuō)徙融,主線程等待dispatch_sync函數(shù)返回,而dispatch_sync函數(shù)將block添加到主線程瑰谜,主線程因?yàn)橐萪ispatch_sync函數(shù)返回才去執(zhí)行block欺冀,主線程被阻塞,沒(méi)有機(jī)會(huì)去執(zhí)行block,發(fā)生死鎖萨脑。
解決上述問(wèn)題的死鎖隐轩,也簡(jiǎn)單,使用Global Dispatch Queue進(jìn)行處理block任務(wù)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
NSLog(@"block middle");
});
a.調(diào)用dispatch_sync()函數(shù)會(huì)立即阻塞調(diào)用時(shí)該函數(shù)所在的線程,全局并發(fā)線程等待dispatch_sync()函數(shù)返回
b.dispatch_sync()函數(shù)追加任務(wù)(即Block代碼塊)到并發(fā)隊(duì)列dispatch_get_global_queue()中
c.并發(fā)隊(duì)列dispatch_get_global_queue()并發(fā)執(zhí)行任務(wù)渤早,block不需等待职车,可以執(zhí)行,執(zhí)行完后鹊杖,dispatch_sync函數(shù)返回
三悴灵、NSOperation和NSOperationQueue
- NSOperation的作用:配合使用NSOperation和NSOperationQueue也能實(shí)現(xiàn)多線程編程
- NSOperation和NSOperationQueue實(shí)現(xiàn)多線程的具體步驟
- 先將需要執(zhí)行的操作封裝到一個(gè)NSOperation對(duì)象中
- 然后將NSOperation對(duì)象添加到NSOperationQueue中
- 系統(tǒng)會(huì)自動(dòng)將NSOperationQueue中的NSOperation取出來(lái),放到線程執(zhí)行
1.NSOperation
NSOperation是個(gè)抽象類骂蓖,并不具備封裝操作的能力积瞒,必須使用它的子類:
- NSInvocationOperation
- NSBlockOperation
- 自定義子類繼承NSOperation,實(shí)現(xiàn)內(nèi)部相應(yīng)的方法
1.1NSInvocationOperation
a.創(chuàng)建NSInvocationOperation對(duì)象
-(id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
b.調(diào)用start方法開(kāi)始執(zhí)行操作
- (void)start;
注意:默認(rèn)情況下登下,調(diào)用了start方法后并不會(huì)開(kāi)一條新線程去執(zhí)行操作茫孔,而是在當(dāng)前線程同步執(zhí)行操作叮喳,只有將NSOperation放到一個(gè)NSOperationQueue中,才會(huì)異步執(zhí)行操作
- 代碼
/**
NSInvocationOperation的使用
*/
- (void)invocationOperation {
// 1.創(chuàng)建操作
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
// 2.啟動(dòng)操作
[op start];
}
- (void)run {
NSLog(@"------%@", [NSThread currentThread]);
}
- 打印結(jié)果:不開(kāi)啟線程缰贝,在當(dāng)前線程執(zhí)行操作
2017-04-17 23:27:16.796 NSOperation子類的基本使用[845:19316] ------<NSThread: 0x600000076b00>{number = 1, name = main}
1.2.NSBlockOperation
a.創(chuàng)建NSBlockOperation對(duì)象
+(id)blockOperationWithBlock:(void (^)(void))block;
b.通過(guò)addExecutionBlock:方法添加更多的操作
-(void)addExecutionBlock:(void (^)(void))block;
c.調(diào)用start方法開(kāi)始執(zhí)行操作
- (void)start;
注意:只要NSBlockOperation封裝的操作數(shù) > 1馍悟,就會(huì)異步執(zhí)行操作
- 代碼
/**
NSBlockOperation的基本使用
*/
- (void)blockOperation {
// 1.創(chuàng)建操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// 在主線程
NSLog(@"下載1------%@", [NSThread currentThread]);
}];
// 2.添加額外的任務(wù)(在子線程執(zhí)行)
[op addExecutionBlock:^{
NSLog(@"下載2------%@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"下載3------%@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"下載4------%@", [NSThread currentThread]);
}];
// 3.啟動(dòng)操作
[op start];
}
- 打印結(jié)果:第一個(gè)任務(wù)在當(dāng)前當(dāng)前線程,后面添加的任務(wù)會(huì)放在新的線程里執(zhí)行
2017-04-17 23:30:54.619 NSOperation子類的基本使用[872:21229] 下載1------<NSThread: 0x6080000645c0>{number = 1, name = main}
2017-04-17 23:30:54.619 NSOperation子類的基本使用[872:21267] 下載3------<NSThread: 0x600000071c80>{number = 4, name = (null)}
2017-04-17 23:30:54.619 NSOperation子類的基本使用[872:21266] 下載2------<NSThread: 0x60800006da80>{number = 3, name = (null)}
2017-04-17 23:30:54.619 NSOperation子類的基本使用[872:21269] 下載4------<NSThread: 0x600000071dc0>{number = 5, name = (null)}
1.3.自定義NSOperation
a.自定義NSOperation的步驟
- 繼承NSOperation
- 重寫- (void)main方法揩瞪,在里面實(shí)現(xiàn)想執(zhí)行的任務(wù)
- 外部創(chuàng)建操作赋朦,并調(diào)用start方法
#import "CustomOperation.h"
@implementation CustomOperation
/**
重新main方法,在這里執(zhí)行任務(wù)
*/
- (void)main {
for (NSInteger i = 0; i<10; i++) {
NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
}
if (self.isCancelled) return;
for (NSInteger i = 0; i<10; i++) {
NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
}
if (self.isCancelled) return;
for (NSInteger i = 0; i<10; i++) {
NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
}
if (self.isCancelled) return;
}
/**
自定義操作的使用
*/
- (void)customOperation {
CustomOperation *op = [CustomOperation new];
[op start];
}
1.4.操作之間的關(guān)系 -- 操作依賴
NSOperation之間可以輕松的設(shè)置依賴來(lái)保證執(zhí)行順序
[operationB addDependency:operationA]; // 操作B依賴于操作A
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download1----%@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download2----%@", [NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download3----%@", [NSThread currentThread]);
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download4----%@", [NSThread currentThread]);
}];
NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download5----%@", [NSThread currentThread]);
}];
// 設(shè)置依賴
[op3 addDependency:op1];
[op3 addDependency:op2];
[op3 addDependency:op4];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue addOperation:op4];
[queue addOperation:op5];
打印結(jié)果:操作3一定是在操作1李破,2宠哄,4執(zhí)行完后才執(zhí)行
2017-04-18 10:55:14.185 OperationDependency[2914:60630] download1----<NSThread: 0x61000007dc00>{number = 3, name = (null)}
2017-04-18 10:55:14.185 OperationDependency[2914:60633] download4----<NSThread: 0x60000007d840>{number = 5, name = (null)}
2017-04-18 10:55:14.185 OperationDependency[2914:60634] download5----<NSThread: 0x60000007c900>{number = 6, name = (null)}
2017-04-18 10:55:14.185 OperationDependency[2914:60632] download2----<NSThread: 0x618000267c40>{number = 4, name = (null)}
2017-04-18 10:55:14.189 OperationDependency[2914:60633] download3----<NSThread: 0x60000007d840>{number = 5, name = (null)}
1.5操作的監(jiān)聽(tīng)
NSOperation可以很容易的設(shè)置監(jiān)聽(tīng)任務(wù)的完成,可以監(jiān)聽(tīng)一個(gè)操作的執(zhí)行完畢
-(void (^)(void))completionBlock;
-(void)setCompletionBlock:(void (^)(void))block;
2嗤攻、NSOperationQueue
NSOperation可以調(diào)用start方法來(lái)執(zhí)行任務(wù)毛嫉,但默認(rèn)是同步執(zhí)行的
如果將NSOperation添加到NSOperationQueue(操作隊(duì)列)中,系統(tǒng)會(huì)自動(dòng)異步執(zhí)行NSOperation中的操作
使用NSOperationQueue分兩步妇菱,隊(duì)列會(huì)根據(jù)系統(tǒng)情況承粤,自行決定是否開(kāi)啟線程
- 創(chuàng)建操作
- 將操作添加到隊(duì)列中
2.1 添加操作到NSOperationQueue中
-(void)addOperation:(NSOperation *)op;
-(void)addOperationWithBlock:(void (^)(void))block;
- 代碼
/**
隊(duì)列添加操作
*/
- (void)queueAddOperation {
// 1.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.創(chuàng)建操作(操作把任務(wù)封裝起來(lái))
// 設(shè)置最大并發(fā)操作數(shù)
// queue.maxConcurrentOperationCount = 2;
//queue.maxConcurrentOperationCount = 1; // 就變成了串行隊(duì)列
// 2.1 創(chuàng)建NSInvocationOperation
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil];
// 2.2 創(chuàng)建NSBlockOperation
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download3 --- %@", [NSThread currentThread]);
}];
[op3 addExecutionBlock:^{
NSLog(@"download4 --- %@", [NSThread currentThread]);
}];
[op3 addExecutionBlock:^{
NSLog(@"download5 --- %@", [NSThread currentThread]);
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download6 --- %@", [NSThread currentThread]);
}];
// 2.3 創(chuàng)建CustomOperation
CustomOperation *op5 = [[CustomOperation alloc] init];
// 3. 添加操作到隊(duì)列中
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue addOperation:op4];
[queue addOperation:op5];
}
- (void)download1 {
NSLog(@"download1 --- %@", [NSThread currentThread]);
}
- (void)download2 {
NSLog(@"download2 --- %@", [NSThread currentThread]);
}
- 打印結(jié)果
2017-04-17 23:43:00.974 NSOperation與NSOperationQueue初步使用[922:27537] download3 --- <NSThread: 0x60000026f040>{number = 6, name = (null)}
2017-04-17 23:43:00.974 NSOperation與NSOperationQueue初步使用[922:27518] download2 --- <NSThread: 0x608000273400>{number = 3, name = (null)}
2017-04-17 23:43:00.974 NSOperation與NSOperationQueue初步使用[922:27517] download1 --- <NSThread: 0x600000267180>{number = 4, name = (null)}
2017-04-17 23:43:00.974 NSOperation與NSOperationQueue初步使用[922:27520] download6 --- <NSThread: 0x60000026ea00>{number = 5, name = (null)}
2017-04-17 23:43:00.974 NSOperation與NSOperationQueue初步使用[922:27680] download4 --- <NSThread: 0x608000273040>{number = 7, name = (null)}
2017-04-17 23:43:00.974 NSOperation與NSOperationQueue初步使用[922:27537] download5 --- <NSThread: 0x60000026f040>{number = 6, name = (null)}
2.2 最大并發(fā)數(shù):同時(shí)執(zhí)行的任務(wù)數(shù)
隊(duì)列是串行還是并行,可以通過(guò)控制最大并發(fā)數(shù)決定
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
// 設(shè)置最大并發(fā)操作數(shù)
queue.maxConcurrentOperationCount = 1; // 就變成了串行隊(duì)列
2.3 隊(duì)列的取消闯团、暫停辛臊、恢復(fù)
和GCD一樣,都可以設(shè)置隊(duì)列的取消房交,暫停和恢復(fù)
- (void)cancelAllOperations;
- (void)setSuspended:(BOOL)b;
四彻舰、補(bǔ)充
1.GCD和NSOperation的區(qū)別
GCD的隊(duì)列類型
- 并發(fā)隊(duì)列
- 自己創(chuàng)建的
- 全局
- 串行隊(duì)列
- 主隊(duì)列
- 自己創(chuàng)建的
NSOperationQueue的隊(duì)列類型
- 主隊(duì)列
- [NSOperationQueue mainQueue]
- 凡是添加到主隊(duì)列中的任務(wù)(NSOperation),都會(huì)放到主線程中執(zhí)行
- 非主隊(duì)列(其他隊(duì)列)
- [[NSOperationQueue alloc] init]
- 同時(shí)包含了:串行候味、并發(fā)功能刃唤,可以通過(guò)設(shè)置最大并發(fā)數(shù)控制是串行還是并發(fā)隊(duì)列
- 添加到這種隊(duì)列中的任務(wù)(NSOperation),就會(huì)自動(dòng)放到子線程中執(zhí)行
主要區(qū)別:
- GCD是純C語(yǔ)言的API,NSOperation是基于GCD的OC版本封裝
- GCD只支持FIFO的隊(duì)列白群,NSOperation可以很方便地調(diào)整執(zhí)行順序尚胞,設(shè)置最大并發(fā)數(shù)量
- NSOperationQueue可以輕松在operation間設(shè)置依賴關(guān)系,而GCD需要些很多代碼才能實(shí)現(xiàn)
- NSOperationQueue支持KVO帜慢,可以檢測(cè)operation是否正在執(zhí)行(isExecuted)笼裳,是否結(jié)束(isFinisn),是否取消(isCancel)
- GCD的執(zhí)行速度比NSOperation快
2.線程間的通信
在iOS開(kāi)發(fā)過(guò)程中,我們一般在主線程里邊進(jìn)行UI刷新崖堤,例如:點(diǎn)擊侍咱、滾動(dòng)、拖拽等事件密幔。我們通常把一些耗時(shí)的操作放在其他線程楔脯,比如說(shuō)圖片下載、文件上傳等耗時(shí)操作胯甩。而當(dāng)我們有時(shí)候在其他線程完成了耗時(shí)操作時(shí)昧廷,需要回到主線程堪嫂,那么就用到了線程之間的通訊。
-
NSThread
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO]
GCD
/**
線程之間的通信
*/
- (void)connectionBetweenThread {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 圖片的網(wǎng)絡(luò)路徑
NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
// 加載圖片
NSData *data = [NSData dataWithContentsOfURL:url];
// 生成圖片
UIImage *image = [UIImage imageWithData:data];
// 回到主線程
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
}
- NSOperationQueue
/**
* 線程之間的通信
*/
- (void)connectionBetweenThread {
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
// 圖片的網(wǎng)絡(luò)路徑
NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
// 加載圖片
NSData *data = [NSData dataWithContentsOfURL:url];
// 生成圖片
UIImage *image = [UIImage imageWithData:data];
// 回到主線程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
}];
}];
}
線程安全
-
1.多線程安全隱患引出
假設(shè)火車站有3個(gè)賣票窗口木柬,余票是1000皆串,賣票窗口3個(gè)線程同一時(shí)刻讀取剩余票數(shù),都是讀取的1000,賣票線程1賣了一張 ,余票變成999眉枕。賣票線程2反應(yīng)慢點(diǎn)恶复,在賣票線程1后面執(zhí)行賣票,因?yàn)橘u票線程2剛開(kāi)始讀取的余票也是1000,所以在賣掉一張后,余額也變成999速挑。賣票線程3反應(yīng)更慢谤牡,在賣票線程2后面執(zhí)行賣票,因?yàn)橘u票線程3剛開(kāi)始讀取的余票也是1000,所以在賣掉一張后,余額依舊也變成999姥宝。所以出現(xiàn)了錯(cuò)誤,本來(lái)賣了3張,可是余票還有999張翅萤。
當(dāng)多個(gè)線程訪問(wèn)同一塊資源時(shí),很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問(wèn)題
比如多個(gè)線程訪問(wèn)同一個(gè)對(duì)象腊满、同一個(gè)變量套么、同一個(gè)文件,并對(duì)這一塊資源進(jìn)行讀寫操作時(shí)碳蛋,容易發(fā)生問(wèn)題
-
2胚泌、多線程安全隱患代碼示例
// 創(chuàng)建線程
// 創(chuàng)建3條線程 - (void)viewDidLoad { [super viewDidLoad]; self.ticketCount = 100; self.thread01 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil]; self.thread01.name = @"售票員01"; self.thread02 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil]; self.thread02.name = @"售票員02"; self.thread03 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil]; self.thread03.name = @"售票員03";
}
```
// 3條線程訪問(wèn)資源
// 3條線程訪問(wèn)資源
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.thread01 start];
[self.thread02 start];
[self.thread03 start];
}
// 賣票方法
- (void)saleTicket {
while (1) {
//@synchronized(self) {
// 先取出總數(shù)
NSInteger count = self.ticketCount;
if (count > 0) {
self.ticketCount = count - 1;
NSLog(@"%@賣了一張票,還剩下%zd張", [NSThread currentThread].name, self.ticketCount);
} else {
NSLog(@"票已經(jīng)賣完了");
break;
}
// }
}
}
-
3.多線程安全隱患解決方案
在線程A讀取數(shù)據(jù)后肃弟,加一把鎖诸迟,別的線程就不能訪問(wèn)了,只允許加鎖的線程A訪問(wèn)愕乎。當(dāng)這個(gè)加鎖線程操作完數(shù)據(jù)后,線程A解鎖壁公,此時(shí)別的線程就能訪問(wèn)了, 假設(shè)輪到線程B訪問(wèn)感论,線程B也先加把鎖,保證只有自己能訪問(wèn)紊册,執(zhí)行完操作再解鎖....就這樣循環(huán)往復(fù)比肄,只要誰(shuí)訪問(wèn)就加把鎖,直到操作結(jié)束后再解鎖囊陡。這就是互斥鎖芳绩。
注意:
1.盡管互斥鎖能夠有效防止因多線程搶奪資源而造成的數(shù)據(jù)安全問(wèn)題,但是其需要消耗大量的CPU資源撞反;
2.而且只有再多條線程搶奪同一塊資源的時(shí)候才使用互斥鎖
-
4.熟悉SDWebImage的實(shí)現(xiàn)原理
以SDWebImage的實(shí)現(xiàn)原理為demo例子妥色,熟悉如何自定義NSOperation, 如何使用NSOperationQueue以及線程之間如何通信遏片,大家自己去理解(這個(gè)demo我對(duì)這位作者文章的學(xué)習(xí)模仿)
模仿SDWebImage的輕量圖片異步下載框架BBSDWebImage