在那一刻订咸,我意識到曼尊,我必須選擇,要么對一切屈服脏嚷,得過且過地生活骆撇,要么就得努力,爭取過上夢想的生活父叙。
進程神郊、線程和以及程序
現(xiàn)代操作系統(tǒng)比如Mac OS X,UNIX趾唱,Linux涌乳,Windows等,都是支持“多任務(wù)”的操作系統(tǒng)甜癞。有了系統(tǒng)級別的支持夕晓,我們應(yīng)該在開發(fā)過程中應(yīng)該盡可能減少用戶等待時間,讓程序盡可能快的完成運算悠咱≌袅荆可是無論是哪種語言開發(fā)的程序最終往往轉(zhuǎn)換成匯編語言進而解釋成機器碼來執(zhí)行。但是機器碼是按順序執(zhí)行的析既,一個復(fù)雜的多步操作只能一步步按順序逐個執(zhí)行躬贡。改變這種狀況可以從下面講到的兩個角度(單核和多核)出發(fā)。
什么叫“多任務(wù)”呢眼坏?簡單地說拂玻,就是操作系統(tǒng)可以同時運行多個任務(wù)。打個比方,你一邊在用瀏覽器上網(wǎng)纺讲,一邊在聽MP3擂仍,一邊在用Word趕作業(yè),這就是多任務(wù)熬甚,至少同時有3個任務(wù)正在運行逢渔。還有很多任務(wù)悄悄地在后臺同時運行著,只是桌面上沒有顯示而已乡括。
現(xiàn)在肃廓,多核CPU已經(jīng)非常普及了,但是诲泌,即使過去的單核CPU盲赊,也可以執(zhí)行多任務(wù)。由于CPU執(zhí)行代碼都是順序執(zhí)行的敷扫,那么哀蘑,單核CPU是怎么執(zhí)行多任務(wù)的呢?
答案就是操作系統(tǒng)輪流讓各個任務(wù)交替執(zhí)行葵第,任務(wù)1執(zhí)行0.01秒绘迁,切換到任務(wù)2,任務(wù)2執(zhí)行0.01秒卒密,再切換到任務(wù)3缀台,執(zhí)行0.01秒……這樣反復(fù)執(zhí)行下去。表面上看哮奇,每個任務(wù)都是交替執(zhí)行的膛腐,但是,由于CPU的執(zhí)行速度實在是太快了鼎俘,我們感覺就像所有任務(wù)都在同時執(zhí)行一樣哲身。即對于單核處理器,可以將多個步驟放到不同的線程而芥,這樣一來用戶完成UI操作后其他后續(xù)任務(wù)在其他線程中律罢,當(dāng)CPU空閑時會繼續(xù)執(zhí)行,而此時對于用戶而言可以繼續(xù)進行其他操作棍丐;
真正的并行執(zhí)行多任務(wù)只能在多核CPU上實現(xiàn)误辑,但是,由于任務(wù)數(shù)量遠遠多于CPU的核心數(shù)量歌逢,所以巾钉,即使是多核CPU操作系統(tǒng),操作系統(tǒng)也會自動把很多任務(wù)輪流調(diào)度到每個核心上執(zhí)行秘案。
對于操作系統(tǒng)來說砰苍,一個任務(wù)就是一個進程(Process)潦匈,比如打開一個瀏覽器就是啟動一個瀏覽器進程,打開一個記事本就啟動了一個記事本進程赚导,打開兩個記事本就啟動了兩個記事本進程茬缩,打開一個Word就啟動了一個Word進程。有些進程還不止同時干一件事吼旧,比如Word凰锡,它可以同時進行打字、拼寫檢查圈暗、打印等事情掂为。在一個進程內(nèi)部,要同時干多件事员串,就需要同時運行多個“子任務(wù)”勇哗,我們把進程內(nèi)的這些“子任務(wù)”稱為線程(Thread)。
由于每個進程至少要干一件事寸齐,所以欲诺,一個進程至少有一個線程。當(dāng)然渺鹦,像Word這種復(fù)雜的進程可以有多個線程瞧栗,多個線程可以同時執(zhí)行,多線程的執(zhí)行方式和多進程是一樣的海铆,也是由操作系統(tǒng)在多個線程之間快速切換,讓每個線程都短暫地交替運行挣惰,看起來就像同時執(zhí)行一樣卧斟。當(dāng)然,真正地同時執(zhí)行多線程需要多核CPU才可能實現(xiàn)憎茂。
進程
狹義定義:進程就是一段程序的執(zhí)行過程珍语。即在系統(tǒng)中正在運行的一個應(yīng)用程序。比如同時打開微信和QQ竖幔,系統(tǒng)會分別啟動2個進程板乙;每個進程之間是獨立的,每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi)拳氢。
廣義定義:進程是一個具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合的一次運行活動募逞。它是操作系統(tǒng)動態(tài)執(zhí)行的基本單元,在傳統(tǒng)的操作系統(tǒng)中馋评,進程既是基本的分配單元放接,也是基本的執(zhí)行單元。
簡單的來講進程的概念主要有兩點:第一留特,進程是一個實體纠脾。每一個進程都有它自己的地址空間玛瘸,一般情況下,包括文本區(qū)域(text region)苟蹈、數(shù)據(jù)區(qū)域(data region)和堆棧(stack region)糊渊。文本區(qū)域存儲處理器執(zhí)行的代碼;數(shù)據(jù)區(qū)域存儲變量和進程執(zhí)行期間使用的動態(tài)分配的內(nèi)存慧脱;堆棧區(qū)域存儲著活動過程調(diào)用的指令和本地變量渺绒。第二,進程是一個“執(zhí)行中的程序”磷瘤。程序是一個沒有生命的實體芒篷,只有處理器賦予程序生命時,它才能成為一個活動的實體采缚,我們稱其為進程针炉。
線程和主線程
一個進程要想執(zhí)行任務(wù),必須得有線程(每一個進程至少要有一條線程扳抽,即主線程)篡帕。
在引入線程的操作系統(tǒng)中,線程是進程中執(zhí)行運算的最小單位贸呢,是進程中的一個實體镰烧,通常都是把進程作為分配資源的基本單位,而把線程作為獨立運行和獨立調(diào)度的基本單位楞陷,由于線程比進程更小怔鳖,基本上不擁有系統(tǒng)資源,故對它的調(diào)度所付出的開銷就會小得多固蛾,能更高效的提高系統(tǒng)多個程序間并發(fā)執(zhí)行的程度结执;一個進程(程序)的所有任務(wù)都在線程中執(zhí)行。
一個程序有且只有一個主線程(UI線程)艾凯,程序啟動時創(chuàng)建(調(diào)用main來啟動)献幔,主線程的生命周期是和應(yīng)用程序綁定,程序退出時趾诗,主線程也停止蜡感;由主線程的唯一性和功能特性个扰,我們不要將耗時的操作放到主線程中燃逻,耗時操作應(yīng)放在子線程(后臺線程,非主線程); 凡是和UI相關(guān)的操作應(yīng)放在主線程中操作律胀。
同一時間內(nèi)贝乎,一個線程只能執(zhí)行一個任務(wù),若要在1個進程中執(zhí)行多個任務(wù)杈笔,那么只能一個個的按順序執(zhí)行這些任務(wù)(線程的串行)。
線程自己不擁有系統(tǒng)資源糕非,只擁有在運行中必不可少的資源蒙具,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源球榆。
多線程
概念:一個進程中可以開啟多條線程,各線程可以并行(同時)執(zhí)行不同的任務(wù)禁筏;
原理:同一時間持钉,一個CPU只能處理一條線程,只有一條線程在工作篱昔,多線程并發(fā)(同時)執(zhí)行每强,其實是CPU快速的在多條線程之間調(diào)度(切換),如果CPU調(diào)度線程的時間足夠快州刽,就造成了多線程并發(fā)執(zhí)行的假象空执;
優(yōu)點:提高程序資源使用效率來提高系統(tǒng)的效率(CPU、內(nèi)存利用率)穗椅;
缺點:創(chuàng)建線程是有開銷的辨绊,iOS下主要成本包括:內(nèi)核數(shù)據(jù)結(jié)構(gòu)(大約1KB)、椘ケ恚空間(子線程512KB门坷,主線程1MB)、創(chuàng)建線程大約需要90毫秒的創(chuàng)建時間,如果開啟大量的線程袍镀,會降低程序的性能(一般最多3到5個)默蚌;線程越多,CPU在調(diào)度線程上的開銷就越大苇羡;程序設(shè)計更加復(fù)雜(比如線程之間的通信绸吸、多線程的數(shù)據(jù)共享);
iOS中多線程的實現(xiàn)方案:
- pthread
- NSThread
- GCD
- NSOperation
線程的幾種狀態(tài)
- 創(chuàng)建:新創(chuàng)建一個線程對象设江;
- 開啟(就緒狀態(tài)):線程對象創(chuàng)建之后惯裕,其他線程調(diào)用了該對象的start方法,該狀態(tài)的線程位于可運行線程池中绣硝,變得可運行,等待獲取CPU的使用權(quán)撑刺;
- 運行:開啟(就緒)狀態(tài)的線程獲取了CPU資源鹉胖,執(zhí)行程序代碼;
- 阻塞:因某種原因放棄CPU使用權(quán)够傍,暫停運行甫菠,直到線程進入就緒狀態(tài),才有機會轉(zhuǎn)到運行狀態(tài)冕屯;線程處理阻塞狀態(tài)時在內(nèi)存中的表現(xiàn)情況是線程被移出可調(diào)度線程池寂诱,此時不可調(diào)度;
-
死亡:線程執(zhí)行完了安聘、因異常退出了run方法或者是強制退出痰洒,這三種情況會導(dǎo)致線程的死亡瓢棒,線程生命周期結(jié)束;
程序
說起進程丘喻,就不得不說下程序脯宿。先看定義:程序是指令和數(shù)據(jù)的有序集合,其本身沒有任何運行的含義泉粉,是一個靜態(tài)的概念连霉。而進程則是在處理機上的一次執(zhí)行過程,它是一個動態(tài)的概念嗡靡。這個不難理解跺撼,其實進程是包含程序的,進程的執(zhí)行離不開程序讨彼,進程中的文本區(qū)域就是代碼區(qū)歉井,也就是程序。
進程和線程比較
- 進程是CPU分配資源和調(diào)度的單位点骑;
- 線程是CPU調(diào)度(執(zhí)行任務(wù))的最小單位酣难,是程序執(zhí)行的最小單元;
- 同一個進程內(nèi)的線程共享進程的所有資源黑滴;
任務(wù)和隊列
概念:
- 任務(wù)(Task):要執(zhí)行的操作憨募;
- 隊列(Queue):隊列是用來管理線程的,隊列里面放著很多的任務(wù)袁辈,來管理這些任務(wù)什么時候在那條線程里去執(zhí)行菜谣;廣義定義為隊列是一種特殊的線性表,特殊之處在于它只允許在表的前端(front)進行刪除操作晚缩,而在表的后端(rear)進行插入操作尾膊,即FIFO(front input front output),和棧一樣荞彼,隊列是一種操作受限制的線性表冈敛。進行插入操作的端稱為隊尾,進行刪除操作的端稱為隊頭鸣皂。
隊列類型:
-
串行隊列(serialQueue):隊列中的線程按順序執(zhí)行(不會同時執(zhí)行)抓谴,即一次只能執(zhí)行一個task;
-
并發(fā)隊列(concurrentQueue):隊列中的線程會并發(fā)執(zhí)行(同時執(zhí)行)寞缝,即一次可以執(zhí)行多個task癌压;
- 主隊列:主線程隊列只有一個線程(主線程),主隊列是串行隊列荆陆;
任務(wù)(同滩届、異步)
- 同步任務(wù)(Synchronous):所謂同步,就是發(fā)出一個功能調(diào)用時被啼,在沒有得到結(jié)果之前帜消,該調(diào)用就不返回或繼續(xù)執(zhí)行后續(xù)操作棠枉。優(yōu)先級高,在線程中有執(zhí)行順序券犁,不會開啟新的線程术健;
- 異步任務(wù)(Asynchronous):異步與同步相對,當(dāng)一個異步過程調(diào)用發(fā)出后粘衬,調(diào)用者在沒有得到結(jié)果之前荞估,就可以繼續(xù)執(zhí)行后續(xù)操作。當(dāng)這個調(diào)用完成后稚新,一般通過狀態(tài)勘伺、通知和回調(diào)來通知調(diào)用者。對于異步調(diào)用褂删,調(diào)用的返回并不受調(diào)用者控制飞醉。優(yōu)先級低,在線程中執(zhí)行沒有順序屯阀,看cpu閑不閑缅帘。在主隊列中不會開啟新的線程,其他隊列會開啟新的線程难衰;
隊列和任務(wù)的區(qū)分
- 同步和異步主要影響:能不能開啟新的線程钦无。
同步:只是在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力盖袭。
異步:可以在新的線程中執(zhí)行任務(wù)失暂,具備開啟新線程的能力。 - 并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
并發(fā):允許多個任務(wù)并發(fā)(同時)執(zhí)行鳄虱。
串行:一個任務(wù)執(zhí)行完畢后弟塞,再執(zhí)行下一個任務(wù)。
以上概念之間關(guān)系抽象理解和舉例實現(xiàn)
- 隊列(queue)當(dāng)做是管道容器拙已。
- 容器(queue)里面可以裝有貨物(task)决记。
- 線程(thread)相當(dāng)于生產(chǎn)線,多線程相當(dāng)于有多條生產(chǎn)線倍踪。
- 同步異步相當(dāng)于貨物(task) 在生產(chǎn)線上是否生成完成系宫。
- 串行隊列和并行隊列是對貨物的處理方式,串行隊列相當(dāng)于只允許一條生產(chǎn)線(thread)處理隊列的任務(wù)(task)惭适,并行隊列相當(dāng)于允許多條生產(chǎn)線(thread)處理隊列的任務(wù)。
從上面看我們想執(zhí)行一個任務(wù)task 楼镐,有兩個要素癞志,queue(容器)和對task的處理方式(同步和異步)。(不需要thread框产,因為queue是自動選擇生產(chǎn)線的thread)凄杯。因此對任務(wù)的處理排列組合有四種情況
序號 | 任務(wù)處理排列組合 |
---|---|
1 | 同步+串行隊列 |
2 | 異步+串行隊列 |
3 | 同步+并發(fā)隊列 |
4 | 異步+并發(fā)隊列 |
(1)任務(wù)處理 同步 + 串行隊列的線程選擇
void syncSerial() {
NSLog(@"current thread %@",[NSThread currentThread]);
/* 1. 創(chuàng)建串行隊列 */
dispatch_queue_t serialQueue1 = dispatch_queue_create("SerialQueue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("SerialQueue2", DISPATCH_QUEUE_SERIAL);
/* 2. 將任務(wù)放到隊列中 */
dispatch_sync(serialQueue1, ^{
NSLog(@"serialQueue1 task1 exe thread--------%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue2, ^{
NSLog(@"serialQueue2 task1 exe thread--------%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue1, ^{
NSLog(@"serialQueue1 task2 exe thread--------%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue2, ^{
NSLog(@"serialQueue2 task2 exe thread--------%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue1, ^{
NSLog(@"serialQueue1 task3 exe thread--------%@",[NSThread currentThread]);
});
}
執(zhí)行結(jié)果
2019-08-28 22:14:00.994993+0800 任務(wù)[797:36742] current thread <NSThread: 0x6000020294c0>{number = 1, name = main}
2019-08-28 22:14:00.995158+0800 任務(wù)[797:36742] serialQueue1 task1 exe thread--------<NSThread: 0x6000020294c0>{number = 1, name = main}
2019-08-28 22:14:00.995276+0800 任務(wù)[797:36742] serialQueue2 task1 exe thread--------<NSThread: 0x6000020294c0>{number = 1, name = main}
2019-08-28 22:14:00.995374+0800 任務(wù)[797:36742] serialQueue1 task2 exe thread--------<NSThread: 0x6000020294c0>{number = 1, name = main}
2019-08-28 22:14:00.995473+0800 任務(wù)[797:36742] serialQueue2 task2 exe thread--------<NSThread: 0x6000020294c0>{number = 1, name = main}
2019-08-28 22:14:00.995563+0800 任務(wù)[797:36742] serialQueue1 task3 exe thread--------<NSThread: 0x6000020294c0>{number = 1, name = main}
在
同步執(zhí)行 + 串行隊列
可以看到:
- 所有任務(wù)都是在當(dāng)前線程(主線程)中執(zhí)行的错洁,并沒有開啟新的線程(
同步執(zhí)行
不具備開啟新線程的能力)。 - 所有任務(wù)都是依次打印的 (
同步任務(wù)
需要等待隊列的任務(wù)執(zhí)行結(jié)束)戒突。 - 在當(dāng)前線程中將隊列的任務(wù)按照加入的順序依次執(zhí)行完畢屯碴,即任務(wù)是按順序執(zhí)行的(
串行隊列
每次只有一個任務(wù)被執(zhí)行,任務(wù)一個接一個按順序執(zhí)行)膊存。
串行隊列中的特殊隊列导而,主隊列MainQueue隔崎,那么同步 + 主隊列對任務(wù)的處理情況會是什么樣的呢今艺?
void syncMainQueue() {
NSLog(@"------start-------");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"download1-------%@",[NSThread currentThread]);
});
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"download1-------%@",[NSThread currentThread]);
});
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"download1-------%@",[NSThread currentThread]);
});
NSLog(@"-------end---------");
}
執(zhí)行結(jié)果
直接崩潰了, 以前這種情況是會發(fā)生死鎖的,現(xiàn)在直接報錯爵卒。 那么, 為什么會發(fā)生這種情況呢虚缎?這里需要探討下,什么時候線程和queue綁定在一起了钓株。
void syncMoreSerial() {
NSLog(@"current thread %@",[NSThread currentThread]);
//1.創(chuàng)建串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
//2.將任務(wù)放到隊列里
dispatch_sync(serialQueue, ^{
NSLog(@"serialQueue task1 exe thread-%@",[NSThread currentThread]);
dispatch_sync(serialQueue, ^{
NSLog(@"serialQueue task2 exe thread-%@",[NSThread currentThread]);
});
});
}
執(zhí)行結(jié)果:
2019-09-02 11:06:34.575166+0800 任務(wù)[4420:131455] current thread <NSThread: 0x6000038348c0>{number = 1, name = main}
2019-09-02 11:06:34.575421+0800 任務(wù)[4420:131455] serialQueue task1 exe thread-<NSThread: 0x6000038348c0>{number = 1, name = main}
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
同樣發(fā)生了崩潰实牡。
我們發(fā)現(xiàn)在主線程中調(diào)用同步sync調(diào)用串行隊列serialQueue ,沒有發(fā)生崩潰轴合。而在串行隊列的task中再次調(diào)用 同步sync調(diào)用串行隊列serialQueue執(zhí)行任務(wù)發(fā)生崩潰了创坞,但是串行隊列的task執(zhí)行是在主線程中的。為什么第一次調(diào)用主隊列調(diào)用task沒問題值桩,而第二次執(zhí)行執(zhí)行task發(fā)生崩潰了呢摆霉?
我們發(fā)現(xiàn)在執(zhí)行task1 的時候,在serierQueue中添加了task2奔坟,同時要求執(zhí)行task2携栋,這個時候,task1還沒有完成咳秉,這違背了串行隊列的含義了婉支,F(xiàn)IFO,崩潰了。
主線程也是同樣的道理澜建,可以這樣理解, 上圖中, 執(zhí)行syncMainQueue這個方法是在主線程中執(zhí)行的, 你可以把它看做一個任務(wù)A, 這個任務(wù)A也是在主隊列中的,那么代碼執(zhí)行到第一個dispatch_sync的時候, 啟動了任務(wù)B, 把任務(wù)B放進了主隊列中, 由于是同步執(zhí)行, 所以, 必須等待任務(wù)B執(zhí)行完了之后才能繼續(xù)向下執(zhí)行, 但是主線程有任務(wù)A, 所以任務(wù)B無法放到主線程中去執(zhí)行,任務(wù)B等待任務(wù)A執(zhí)行, 任務(wù)A等待任務(wù)B執(zhí)行, 這樣就造成了死鎖向挖。
我們再來看個例子:
void syncMoreSerial() {
NSLog(@"current thread %@",[NSThread currentThread]);
//1.創(chuàng)建串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue1 = dispatch_queue_create("serialQueue1", DISPATCH_QUEUE_SERIAL);
//2.將任務(wù)放到隊列里
dispatch_sync(serialQueue, ^{
NSLog(@"serialQueue task1 exe thread-%@",[NSThread currentThread]);
dispatch_sync(serialQueue1, ^{
NSLog(@"serialQueue1 task1 exe thread-%@",[NSThread currentThread]);
});
});
}
執(zhí)行結(jié)果:
2019-09-02 11:13:56.558896+0800 任務(wù)[4486:142175] current thread <NSThread: 0x600001816900>{number = 1, name = main}
2019-09-02 11:13:56.559102+0800 任務(wù)[4486:142175] serialQueue task1 exe thread-<NSThread: 0x600001816900>{number = 1, name = main}
2019-09-02 11:13:56.559211+0800 任務(wù)[4486:142175] serialQueue1 task1 exe thread-<NSThread: 0x600001816900>{number = 1, name = main}
上面這個實例和前面的syncMoreSerial實例唯一的區(qū)別就是serialQueue task1和serialQueue task2分別在不同隊列中,
這樣互不影響炕舵,就不會出現(xiàn)崩潰死鎖現(xiàn)象何之,這讓我們更好理解同步+串行隊列的內(nèi)容。
(2)任務(wù)處理 異步+串行隊列的線程選擇
void asyncSerial() {
NSLog(@"current thread %@",[NSThread currentThread]);
/* 1. 創(chuàng)建串行隊列 */
dispatch_queue_t serialQueue = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("SerialQueue2", DISPATCH_QUEUE_SERIAL);
/* 2. 將任務(wù)放到隊列中 */
dispatch_async(serialQueue, ^{
NSLog(@"serialQueue task1 exe thread--------%@",[NSThread currentThread]);
});
dispatch_async(serialQueue2, ^{
NSLog(@"serialQueue2 task1 exe thread--------%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"serialQueue task2 exe thread--------%@",[NSThread currentThread]);
});
dispatch_async(serialQueue2, ^{
NSLog(@"serialQueue2 task2 exe thread--------%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"serialQueue task3 exe thread--------%@",[NSThread currentThread]);
});
}
執(zhí)行結(jié)果:
2019-09-02 12:56:21.371933+0800 任務(wù)[5177:205067] current thread <NSThread: 0x600002c35380>{number = 1, name = main}
2019-09-02 12:56:21.372169+0800 任務(wù)[5177:205143] serialQueue task1 exe thread--------<NSThread: 0x600002c5c780>{number = 3, name = (null)}
2019-09-02 12:56:21.372197+0800 任務(wù)[5177:205145] serialQueue2 task1 exe thread--------<NSThread: 0x600002c5ca00>{number = 4, name = (null)}
2019-09-02 12:56:21.372291+0800 任務(wù)[5177:205143] serialQueue task2 exe thread--------<NSThread: 0x600002c5c780>{number = 3, name = (null)}
2019-09-02 12:56:21.372326+0800 任務(wù)[5177:205145] serialQueue2 task2 exe thread--------<NSThread: 0x600002c5ca00>{number = 4, name = (null)}
2019-09-02 12:56:21.372513+0800 任務(wù)[5177:205143] serialQueue task3 exe thread--------<NSThread: 0x600002c5c780>{number = 3, name = (null)}
由上可以看出異步執(zhí)行 + 串行隊列中串行隊列的thread不是主線程了咽筋,而是一條新開辟的線程溶推,但是不管任務(wù)有多少個任務(wù),異步執(zhí)行 + 串行隊列(同一條串行隊列只開啟一條新的線程),任務(wù)的執(zhí)行順序也是按照隊列中的順序執(zhí)行的,因為同一條線程中蒜危,必須等到前一個任務(wù)執(zhí)行完畢后虱痕,才能執(zhí)行下一個任務(wù)。但是串行隊列對應(yīng)的任務(wù)是可能在多條線程中執(zhí)行辐赞,多個串行隊列的開辟的線程之間是可以共享使用的部翘。
在 異步執(zhí)行 + 串行隊列
可以看到:
- 開啟了一條新線程(
異步執(zhí)行
具備開啟新線程的能力,串行隊列
只開啟一個線程)响委。 - 所有任務(wù)是在打印的
syncConcurrent---begin
和syncConcurrent---end
之后才開始執(zhí)行的(異步執(zhí)行
不會做任何等待新思,可以繼續(xù)執(zhí)行任務(wù))。 - 任務(wù)是按順序執(zhí)行的(
串行隊列
每次只有一個任務(wù)被執(zhí)行晃酒,任務(wù)一個接一個按順序執(zhí)行)表牢。
串行隊列中的特殊隊列,主隊列MainQueue贝次,那么異步 + 主隊列對任務(wù)的處理情況會是什么樣的呢崔兴?即主隊列在主線程中執(zhí)行異步什么情況呢?
void asyncMainQueue() {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"download1------%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"download2------%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"download3------%@",[NSThread currentThread]);
});
}
運行結(jié)果:
2019-09-02 13:21:20.124919+0800 任務(wù)[5355:235069] download1------<NSThread: 0x6000030ff780>{number = 1, name = main}
2019-09-02 13:21:20.125129+0800 任務(wù)[5355:235069] download2------<NSThread: 0x6000030ff780>{number = 1, name = main}
2019-09-02 13:21:20.125332+0800 任務(wù)[5355:235069] download3------<NSThread: 0x6000030ff780>{number = 1, name = main}
異步執(zhí)行雖然有開啟新線程的能力, 但是異步執(zhí)行 + 主隊列并不會開啟新的線程, 任務(wù)都是在主線程中執(zhí)行的蛔翅。
(3)任務(wù)處理 同步+并發(fā)隊列的線程選擇
void syncConcurrent() {
NSLog(@"current thread %@",[NSThread currentThread]);
/* 1. 創(chuàng)建一條并發(fā)隊列 */
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t concurrentQueue2 = dispatch_queue_create("concurrentQueue2", DISPATCH_QUEUE_CONCURRENT);
/* 2. 把任務(wù)放到隊列中 */
dispatch_sync(concurrentQueue, ^{
NSLog(@"concurrentQueue task1 exe thread--------%@",[NSThread currentThread]);
});
dispatch_sync(concurrentQueue2, ^{
NSLog(@"concurrentQueue2 task1 exe thread--------%@",[NSThread currentThread]);
});
dispatch_sync(concurrentQueue, ^{
NSLog(@"concurrentQueue task2 exe thread--------%@",[NSThread currentThread]);
});
dispatch_sync(concurrentQueue2, ^{
NSLog(@"concurrentQueue2 task2 exe thread--------%@",[NSThread currentThread]);
});
dispatch_sync(concurrentQueue, ^{
NSLog(@"concurrentQueue task3 exe thread--------%@",[NSThread currentThread]);
});
}
執(zhí)行結(jié)果:
2019-09-02 16:10:13.360714+0800 任務(wù)[11194:422301] current thread <NSThread: 0x60000299d3c0>{number = 1, name = main}
2019-09-02 16:10:13.360871+0800 任務(wù)[11194:422301] concurrentQueue task1 exe thread--------<NSThread: 0x60000299d3c0>{number = 1, name = main}
2019-09-02 16:10:13.361007+0800 任務(wù)[11194:422301] concurrentQueue2 task1 exe thread--------<NSThread: 0x60000299d3c0>{number = 1, name = main}
2019-09-02 16:10:13.361111+0800 任務(wù)[11194:422301] concurrentQueue task2 exe thread--------<NSThread: 0x60000299d3c0>{number = 1, name = main}
2019-09-02 16:10:13.361213+0800 任務(wù)[11194:422301] concurrentQueue2 task2 exe thread--------<NSThread: 0x60000299d3c0>{number = 1, name = main}
2019-09-02 16:10:13.361308+0800 任務(wù)[11194:422301] concurrentQueue task3 exe thread--------<NSThread: 0x60000299d3c0>{number = 1, name = main}
從同步執(zhí)行 + 并發(fā)隊列
中可看到:
- 所有任務(wù)都是在當(dāng)前線程(主線程)中按照順序依次執(zhí)行的敲茄,沒有開啟新的線程(同步執(zhí)行不具備開啟新線程的能力)。
- 所有任務(wù)都是依次打印的(同步任務(wù) 需要等待隊列的任務(wù)執(zhí)行結(jié)束)山析。
- 任務(wù)按順序執(zhí)行的堰燎。按順序執(zhí)行的原因:雖然
并發(fā)隊列
可以開啟多個線程,并且同時執(zhí)行多個任務(wù)笋轨。但是因為本身不能創(chuàng)建新線程秆剪,只有當(dāng)前線程這一個線程(同步任務(wù)
不具備開啟新線程的能力),所以也就不存在并發(fā)爵政。而且當(dāng)前線程只有等待當(dāng)前隊列中正在執(zhí)行的任務(wù)執(zhí)行完畢之后仅讽,才能繼續(xù)接著執(zhí)行下面的操作(同步任務(wù)
需要等待隊列的任務(wù)執(zhí)行結(jié)束)。所以任務(wù)只能一個接一個按順序執(zhí)行钾挟,不能同時被執(zhí)行洁灵。
(4)任務(wù)處理 異步+并發(fā)隊列的線程選擇
void asyncConcurrent() {
NSLog(@"current thread %@",[NSThread currentThread]);
/* 1. 創(chuàng)建一條并發(fā)隊列 */
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t concurrentQueue2 = dispatch_queue_create("concurrentQueue2", DISPATCH_QUEUE_CONCURRENT);
/* 2. 把任務(wù)放到隊列中 */
dispatch_async(concurrentQueue, ^{
NSLog(@"concurrentQueue task1 exe thread--------%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue2, ^{
NSLog(@"concurrentQueue2 task1 exe thread--------%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"concurrentQueue task2 exe thread--------%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue2, ^{
NSLog(@"concurrentQueue2 task2 exe thread--------%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"concurrentQueue task3 exe thread--------%@",[NSThread currentThread]);
});
}
執(zhí)行結(jié)果:
2019-09-02 16:34:33.406510+0800 任務(wù)[11384:446153] current thread <NSThread: 0x600003991400>{number = 1, name = main}
2019-09-02 16:34:33.406761+0800 任務(wù)[11384:446280] concurrentQueue2 task1 exe thread--------<NSThread: 0x6000039cbc40>{number = 4, name = (null)}
2019-09-02 16:34:33.406763+0800 任務(wù)[11384:446276] concurrentQueue task1 exe thread--------<NSThread: 0x6000039f4740>{number = 3, name = (null)}
2019-09-02 16:34:33.406794+0800 任務(wù)[11384:446277] concurrentQueue task2 exe thread--------<NSThread: 0x6000039cbcc0>{number = 5, name = (null)}
2019-09-02 16:34:33.406891+0800 任務(wù)[11384:446279] concurrentQueue task3 exe thread--------<NSThread: 0x6000039cbd40>{number = 7, name = (null)}
2019-09-02 16:34:33.406803+0800 任務(wù)[11384:446278] concurrentQueue2 task2 exe thread--------<NSThread: 0x6000039f4780>{number = 6, name = (null)}
并發(fā)隊列concurrentQueue分別執(zhí)行在了三條線程上。并發(fā)隊列concurrentQueue2分別執(zhí)行在兩條線程上掺出。并發(fā)隊列創(chuàng)建的線程之間是可以共享使用的徽千,看如下執(zhí)行結(jié)果:
執(zhí)行結(jié)果:
2019-09-02 16:38:04.019812+0800 任務(wù)[11421:451122] current thread <NSThread: 0x600000042940>{number = 1, name = main}
2019-09-02 16:38:04.020140+0800 任務(wù)[11421:451244] concurrentQueue task2 exe thread--------<NSThread: 0x600000026680>{number = 6, name = (null)}
2019-09-02 16:38:04.020163+0800 任務(wù)[11421:451242] concurrentQueue2 task2 exe thread--------<NSThread: 0x600000026600>{number = 5, name = (null)}
2019-09-02 16:38:04.020171+0800 任務(wù)[11421:451241] concurrentQueue2 task1 exe thread--------<NSThread: 0x60000001d940>{number = 4, name = (null)}
2019-09-02 16:38:04.020201+0800 任務(wù)[11421:451243] concurrentQueue task1 exe thread--------<NSThread: 0x6000000265c0>{number = 3, name = (null)}
2019-09-02 16:38:04.020467+0800 任務(wù)[11421:451241] concurrentQueue task3 exe thread--------<NSThread: 0x60000001d940>{number = 4, name = (null)}
這里線程4被復(fù)用了。這是因為線程4任務(wù)已經(jīng)執(zhí)行完畢concurrentQueue2 task1的任務(wù)空閑下來了汤锨。隊列會先利用空閑下來的線程而不是去創(chuàng)建双抽。當(dāng)然系統(tǒng)創(chuàng)建線程也是有限制的,那么系統(tǒng)做多會創(chuàng)建多少條線程呢闲礼?
void asyncMoreConcurrent() {
NSLog(@"current thread %@",[NSThread currentThread]);
/* 1. 創(chuàng)建一條并發(fā)隊列 */
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
for (int i =0; i<1000; i++) {
dispatch_async(concurrentQueue, ^{
NSLog(@"%d concurrentQueue task1 exe thread--------%@",i,[NSThread currentThread]);
sleep(100);
});
}
}
執(zhí)行結(jié)果:
2019-09-02 16:47:50.451507+0800 任務(wù)[11511:464499] 58 concurrentQueue task1 exe thread--------<NSThread: 0x600003c72c00>{number = 61, name = (null)}
2019-09-02 16:47:50.451939+0800 任務(wù)[11511:464501] 60 concurrentQueue task1 exe thread--------<NSThread: 0x600003c94200>{number = 63, name = (null)}
2019-09-02 16:47:50.451583+0800 任務(wù)[11511:464500] 59 concurrentQueue task1 exe thread--------<NSThread: 0x600003c72c80>{number = 62, name = (null)}
2019-09-02 16:47:50.451940+0800 任務(wù)[11511:464502] 61 concurrentQueue task1 exe thread--------<NSThread: 0x600003c94280>{number = 64, name = (null)}
2019-09-02 16:47:50.452695+0800 任務(wù)[11511:464503] 62 concurrentQueue task1 exe thread--------<NSThread: 0x600003c94340>{number = 66, name = (null)}
2019-09-02 16:47:50.452649+0800 任務(wù)[11511:464504] 63 concurrentQueue task1 exe thread--------<NSThread: 0x600003c94380>{number = 65, name = (null)}
在 異步執(zhí)行 + 并發(fā)隊列
中可以看出:
- 除了當(dāng)前線程(主線程)牍汹,系統(tǒng)又開啟了 3 個線程琅翻,并且任務(wù)是交替/同時執(zhí)行的。(
異步執(zhí)行
具備開啟新線程的能力柑贞。且并發(fā)隊列
可開啟多個線程,同時執(zhí)行多個任務(wù))聂抢。 - 所有任務(wù)是在打印的
syncConcurrent---begin
和syncConcurrent---end
之后才執(zhí)行的钧嘶。說明當(dāng)前線程沒有等待,而是直接開啟了新線程琳疏,在新線程中執(zhí)行任務(wù)(異步執(zhí)行
不做等待有决,可以繼續(xù)執(zhí)行任務(wù))。
執(zhí)行方式匯總
最后還有一個系統(tǒng)以及為我們創(chuàng)建好的并發(fā)隊列空盼,全局并發(fā)隊列书幕,首先, 它是一個并發(fā)隊列, 他是系統(tǒng)為我們創(chuàng)建好的一個全局的并發(fā)隊列, 所以, 有時候, 我們不需要自己創(chuàng)建一個并發(fā)隊列, 直接用系統(tǒng)為我們提供的全局隊列就可以了,所以全局隊列和同步執(zhí)行以及異步執(zhí)行的組合同并發(fā)隊列是一樣的。
本文參考于 http://www.reibang.com/p/8a6141c893d9, 如有侵權(quán)揽趾,請原著與我溝通台汇,可以立即取消發(fā)布。